/**
 * This file was generated by @pikku/cli@0.12.23
 */
/**
 * Workflow HTTP catch-all routes
 * Do not edit manually - regenerate with 'npx pikku'
 */
import { pikkuSessionlessFunc, wireHTTPRoutes } from '@project/functions/.pikku/pikku-types.gen.js'
import { MissingServiceError } from '@pikku/core/errors'
import type { WorkflowRunStatus } from '@pikku/core/workflow'

function assertWorkflowService(workflowService: unknown): asserts workflowService {
  if (!workflowService) throw new MissingServiceError('workflowService is required')
}

function assertWorkflowRunService(workflowRunService: unknown): asserts workflowRunService {
  if (!workflowRunService) throw new MissingServiceError('workflowRunService is required')
}

export const workflowStarter = pikkuSessionlessFunc<
  { workflowName: string; data?: unknown },
  { runId: string }
>({
  tags: ['pikku'],
  auth: false,
  // workflowService is destructured (even though we delegate via rpc) so the
  // analyzer assigns workflow-state capability to this unit — without it,
  // rpc.startWorkflow() runs against a container missing workflowService.
  func: async ({ workflowService }, { workflowName, data }, { rpc }) => {
    assertWorkflowService(workflowService)
    return await rpc.startWorkflow(workflowName as any, (data ?? {}) as any)
  },
})

export const workflowRunner = pikkuSessionlessFunc<
  { workflowName: string; data?: unknown },
  unknown
>({
  tags: ['pikku'],
  auth: false,
  func: async ({ workflowService }, { workflowName, data }, { rpc }) => {
    assertWorkflowService(workflowService)
    return await workflowService.runToCompletion(workflowName, data ?? {}, rpc)
  },
})

export const workflowStatusChecker = pikkuSessionlessFunc<
  { workflowName: string; runId: string },
  WorkflowRunStatus
>({
  tags: ['pikku'],
  auth: false,
  func: async ({ workflowService }, { runId }) => {
    assertWorkflowService(workflowService)
    const status = await workflowService.getRunStatus(runId)
    if (!status) throw new Error(`Run not found: ${runId}`)
    return status
  },
})

/**
 * Minimal workflow status stream — sends step names and statuses only.
 * Use this for user-facing frontends where internal details should not be exposed.
 */
export const workflowStatusStream = pikkuSessionlessFunc<
  { workflowName: string; runId: string },
  unknown
>({
  tags: ['pikku'],
  auth: false,
  func: async ({ workflowRunService }, { runId }, { channel }) => {
    assertWorkflowRunService(workflowRunService)
    if (!channel) return

    const terminalStatuses = new Set(['completed', 'failed', 'cancelled'])
    let lastHash = ''
    let initSent = false

    const poll = async () => {
      const run = await workflowRunService.getRun(runId)
      if (!run) {
        channel.close()
        return false
      }

      const steps = await workflowRunService.getRunSteps(runId)

      if (!initSent && run.deterministic) {
        const statusByStep = new Map(
          steps.map((s: { stepName: string; status: string }) => [
            s.stepName,
            s.status,
          ])
        )
        channel.send({
          type: 'init',
          deterministic: true,
          steps: (run.plannedSteps ?? []).map(
            (s: { stepName: string }) => ({
              stepName: s.stepName,
              status: statusByStep.get(s.stepName) ?? 'pending',
            })
          ),
        })
        initSent = true
      }

      const hash = JSON.stringify({
        s: run.status,
        steps: steps.map((s: { stepName: string; status: string }) => [s.stepName, s.status]),
      })

      if (hash !== lastHash) {
        lastHash = hash
        channel.send({
          type: 'update',
          status: run.status,
          steps: steps.map((s: { stepName: string; status: string }) => ({
            stepName: s.stepName,
            status: s.status,
          })),
        })
      }

      if (terminalStatuses.has(run.status)) {
        channel.send({ type: 'done' })
        channel.close()
        return false
      }
      return true
    }

    const shouldContinue = await poll()
    if (!shouldContinue) return

    await new Promise<void>((resolve) => {
      const interval = setInterval(async () => {
        const cont = await poll()
        if (!cont) {
          clearInterval(interval)
          resolve()
        }
      }, 500)
    })
  },
})

/**
 * Full workflow status stream — includes output, error, and child run IDs.
 * Use this for admin consoles and internal tooling.
 */
export const workflowStatusStreamFull = pikkuSessionlessFunc<
  { workflowName: string; runId: string },
  unknown
>({
  tags: ['pikku'],
  auth: false,
  func: async ({ workflowRunService }, { runId }, { channel }) => {
    assertWorkflowRunService(workflowRunService)
    if (!channel) return

    const terminalStatuses = new Set(['completed', 'failed', 'cancelled'])
    let lastHash = ''
    let initSent = false

    const poll = async () => {
      const run = await workflowRunService.getRun(runId)
      if (!run) {
        channel.close()
        return false
      }

      const steps = await workflowRunService.getRunSteps(runId)

      if (!initSent && run.deterministic) {
        const statusByStep = new Map(
          steps.map((s: { stepName: string; status: string }) => [
            s.stepName,
            s.status,
          ])
        )
        channel.send({
          type: 'init',
          deterministic: true,
          steps: (run.plannedSteps ?? []).map(
            (s: { stepName: string }) => ({
              stepName: s.stepName,
              status: statusByStep.get(s.stepName) ?? 'pending',
            })
          ),
        })
        initSent = true
      }

      const hash = JSON.stringify({
        s: run.status,
        o: run.output,
        steps: steps.map((s: { stepName: string; status: string }) => [s.stepName, s.status]),
      })

      if (hash !== lastHash) {
        lastHash = hash
        channel.send({
          type: 'update',
          status: run.status,
          output: run.output,
          error: run.error,
          steps: steps.map((s: { stepName: string; status: string; childRunId?: string }) => ({
            stepName: s.stepName,
            status: s.status,
            ...(s.childRunId ? { childRunId: s.childRunId } : {}),
          })),
        })
      }

      if (terminalStatuses.has(run.status)) {
        channel.send({ type: 'done' })
        channel.close()
        return false
      }
      return true
    }

    const shouldContinue = await poll()
    if (!shouldContinue) return

    await new Promise<void>((resolve) => {
      const interval = setInterval(async () => {
        const cont = await poll()
        if (!cont) {
          clearInterval(interval)
          resolve()
        }
      }, 500)
    })
  },
})

export const graphStarter = pikkuSessionlessFunc<
  { workflowName: string; nodeId: string; data?: unknown },
  { runId: string }
>({
  auth: false,
  // See workflowStarter — destructure workflowService so the analyzer
  // assigns workflow-state capability to this unit.
  func: async ({ workflowService }, { workflowName, nodeId, data }, { rpc }) => {
    assertWorkflowService(workflowService)
    return await rpc.startWorkflow(workflowName as any, (data ?? {}) as any, { startNode: nodeId })
  },
})

wireHTTPRoutes({
  auth: false,
  routes: {
    workflowStart: {
      route: '/workflow/:workflowName/start',
      method: 'post',
      func: workflowStarter,
    },
    workflowRun: {
      route: '/workflow/:workflowName/run',
      method: 'post',
      func: workflowRunner,
    },
    workflowStatus: {
      route: '/workflow/:workflowName/status/:runId',
      method: 'get',
      func: workflowStatusChecker,
    },
    workflowStatusStream: {
      route: '/workflow/:workflowName/status/:runId/stream',
      method: 'get',
      sse: true,
      func: workflowStatusStream,
    },
    workflowStatusStreamFull: {
      route: '/workflow/:workflowName/status/:runId/stream/full',
      method: 'get',
      sse: true,
      func: workflowStatusStreamFull,
    },
    graphStart: {
      route: '/workflow/:workflowName/graph/:nodeId',
      method: 'post',
      func: graphStarter,
    },
  },
})
