import { Elysia, t } from 'elysia'

import { AppError } from '../../errors/app-error'
import * as Issue from '../issue/service'
import { WorktreeModel } from '../worktree/model'
import * as Worktree from '../worktree/service'
import { sessionArchiveChunks } from './export-archive'
import { SessionModel } from './model'
import { isRemoteProjectedSession, syncRemoteSessionTitle } from './remote-projection'
import * as Session from './service'

export const session = new Elysia({
  prefix: '/sessions',
  detail: { tags: ['session'] },
})
  .get('/', ({ query }) => {
    const sessions = Session.list(query)
    // Best-effort: sync titles from remote host for any remote-projected sessions.
    for (const s of sessions) {
      if (s.execution.kind === 'remote-host') {
        void syncRemoteSessionTitle(s.id)
      }
    }
    return sessions
  }, {
    detail: {
      'summary': 'List sessions',
      'x-cradle-cli': {
        command: ['session', 'list'],
      },
    },
    query: SessionModel.listQuery,
    response: { 200: t.Array(SessionModel.session) },
  })
  .get(
    '/:id',
    ({ params }) => {
      const s = Session.get(params.id)
      if (!s) {
        throw new AppError({ code: 'session_not_found', status: 404, message: 'Session not found' })
      }
      // Best-effort: sync title from remote host so the local projection stays fresh.
      if (isRemoteProjectedSession(params.id)) {
        void syncRemoteSessionTitle(params.id)
      }
      return s
    },
    {
      detail: {
        'summary': 'Get session by ID',
        'x-cradle-cli': {
          command: ['session', 'get'],
        },
      },
      params: SessionModel.idParams,
      response: { 200: SessionModel.session },
    },
  )
  .post('/', async ({ body }) => await Session.create(body), {
    detail: {
      'summary': 'Create session',
      'x-cradle-cli': {
        command: ['session', 'create'],
      },
    },
    body: SessionModel.createBody,
    response: { 200: SessionModel.session },
  })
  .patch(
    '/:id',
    async ({ params, body }) => {
      if (
        body.title === undefined
        && body.pinned === undefined
        && body.providerTargetId === undefined
        && body.modelId === undefined
        && body.thinkingEffort === undefined
        && body.sessionGroupId === undefined
      ) {
        throw new AppError({
          code: 'invalid_session_input',
          status: 400,
          message: 'at least one session field is required',
        })
      }
      const result = await Session.update({ id: params.id, ...body })
      if (!result) {
        throw new AppError({ code: 'session_not_found', status: 404, message: 'Session not found' })
      }
      return result
    },
    {
      detail: {
        'summary': 'Update session',
        'x-cradle-cli': {
          command: ['session', 'update'],
        },
      },
      params: SessionModel.idParams,
      body: SessionModel.updateBody,
      response: { 200: SessionModel.session },
    },
  )
  .post(
    '/:id/archive',
    async ({ params, body }) => {
      const result = await Session.setArchived({ id: params.id, archived: body.archived })
      if (!result) {
        throw new AppError({ code: 'session_not_found', status: 404, message: 'Session not found' })
      }
      return result
    },
    {
      detail: {
        'summary': 'Archive or restore session',
        'x-cradle-cli': {
          command: ['session', 'archive'],
        },
      },
      params: SessionModel.idParams,
      body: SessionModel.archiveBody,
      response: { 200: SessionModel.session },
    },
  )
  .post(
    '/:id/read',
    ({ params }) => {
      const result = Session.markRead(params.id)
      if (!result) {
        throw new AppError({ code: 'session_not_found', status: 404, message: 'Session not found' })
      }
      return result
    },
    {
      detail: {
        summary: 'Mark session as read',
      },
      params: SessionModel.idParams,
      response: { 200: SessionModel.session },
    },
  )
  .post(
    '/:id/unread',
    ({ params }) => {
      const result = Session.markUnread(params.id)
      if (!result) {
        throw new AppError({ code: 'session_not_found', status: 404, message: 'Session not found' })
      }
      return result
    },
    {
      detail: {
        summary: 'Mark session as unread',
      },
      params: SessionModel.idParams,
      response: { 200: SessionModel.session },
    },
  )
  .delete(
    '/:id',
    async ({ params }) => {
      await Session.remove(params.id)
      return { ok: true as const }
    },
    {
      detail: {
        'summary': 'Delete session',
        'x-cradle-cli': {
          command: ['session', 'delete'],
        },
      },
      params: SessionModel.idParams,
      response: { 200: t.Object({ ok: t.Literal(true) }) },
    },
  )
  .get('/:id/export/markdown', ({ params }) => ({ markdown: Session.exportMarkdown(params.id) }), {
    detail: {
      'summary': 'Export session as markdown',
      'x-cradle-cli': {
        command: ['session', 'export', 'markdown'],
      },
    },
    params: SessionModel.idParams,
    response: { 200: SessionModel.exportMarkdownResponse },
  })
  .get('/:id/export/zip', ({ params }) => {
    // exportArchive throws AppError 404 (not found) / 409 (still streaming)
    // synchronously, which Elysia maps through the standard error handler.
    const { archive, filename } = Session.exportArchive(params.id)
    const stream = new ReadableStream<Uint8Array>({
      async start(controller) {
        try {
          for await (const chunk of sessionArchiveChunks(archive)) {
            controller.enqueue(chunk)
          }
          controller.close()
        }
        catch (error) {
          controller.error(error)
        }
      },
    })
    return new Response(stream, {
      headers: {
        'content-type': 'application/zip',
        'content-disposition': `attachment; filename="${filename}"`,
        'cache-control': 'no-store',
      },
    })
  }, {
    detail: {
      'summary': 'Export session as zip archive (session.json + transcript.md)',
      'x-cradle-cli': {
        command: ['session', 'export', 'zip'],
      },
    },
    params: SessionModel.idParams,
  })

  // ── linked issue ──
  .get('/:id/linked-issue', ({ params }) => Issue.getLinkedIssue(params.id), {
    detail: {
      'summary': 'Get linked issue',
      'x-cradle-cli': {
        command: ['session', 'linked-issue', 'get'],
        defaultChatSessionId: true,
      },
    },
    params: SessionModel.idParams,
    response: { 200: t.Object({ issueId: t.Nullable(t.String()) }) },
  })
  .post('/:id/linked-issue', ({ params, body }) => Issue.linkIssue(params.id, body.issueId), {
    detail: {
      'summary': 'Link issue to session',
      'x-cradle-cli': {
        command: ['session', 'linked-issue', 'link'],
        defaultChatSessionId: true,
      },
    },
    params: SessionModel.idParams,
    body: t.Object({ issueId: t.String({ minLength: 1 }) }),
    response: { 200: t.Object({ ok: t.Literal(true) }) },
  })
  .delete('/:id/linked-issue', ({ params }) => Issue.unlinkIssue(params.id), {
    detail: {
      'summary': 'Unlink issue from session',
      'x-cradle-cli': {
        command: ['session', 'linked-issue', 'unlink'],
        defaultChatSessionId: true,
      },
    },
    params: SessionModel.idParams,
    response: { 200: t.Object({ ok: t.Literal(true) }) },
  })
  .post('/:id/isolation/start', async ({ params, body }) => {
    return await Worktree.startSessionIsolation({
      sessionId: params.id,
      slug: body.slug,
    })
  }, {
    detail: {
      'summary': 'Start session isolation',
      'x-cradle-cli': {
        command: ['session', 'isolation', 'start'],
        defaultChatSessionId: true,
      },
    },
    params: SessionModel.idParams,
    body: SessionModel.isolationStartBody,
    response: { 200: SessionModel.isolationStartResponse },
  })
  .post('/:id/isolation/activate', async ({ params, body }) => {
    await Worktree.activateSessionIsolation({
      sessionId: params.id,
      mode: body.mode,
    })
    return { ok: true as const }
  }, {
    detail: {
      'summary': 'Activate pending session isolation',
      'x-cradle-cli': {
        command: ['session', 'isolation', 'activate'],
        defaultChatSessionId: true,
      },
    },
    params: SessionModel.idParams,
    body: SessionModel.isolationActivateBody,
    response: { 200: t.Object({ ok: t.Literal(true) }) },
  })
  .post('/:id/isolation/cancel', async ({ params }) => {
    await Worktree.activateSessionIsolation({
      sessionId: params.id,
      mode: 'cancel',
    })
    return { ok: true as const }
  }, {
    detail: {
      'summary': 'Cancel pending session isolation',
      'x-cradle-cli': {
        command: ['session', 'isolation', 'cancel'],
        defaultChatSessionId: true,
      },
    },
    params: SessionModel.idParams,
    response: { 200: t.Object({ ok: t.Literal(true) }) },
  })
  .post('/:id/isolation/leave', ({ params }) => {
    Worktree.leaveSessionIsolation(params.id)
    return { ok: true as const }
  }, {
    detail: {
      'summary': 'Leave isolated execution for session',
      'x-cradle-cli': {
        command: ['session', 'isolation', 'leave'],
        defaultChatSessionId: true,
      },
    },
    params: SessionModel.idParams,
    response: { 200: t.Object({ ok: t.Literal(true) }) },
  })
  .post('/:id/isolation/repair', async ({ params }) => {
    const worktree = await Worktree.repairSessionWorktree(params.id)
    return { worktree }
  }, {
    detail: {
      'summary': 'Recreate missing isolated checkout for session',
      'x-cradle-cli': {
        command: ['session', 'isolation', 'repair'],
        defaultChatSessionId: true,
      },
    },
    params: SessionModel.idParams,
    response: { 200: t.Object({ worktree: WorktreeModel.worktreeView }) },
  })
  .post('/:id/isolation/attach', ({ params, body }) => {
    Worktree.attachSessionToWorktree({
      sessionId: params.id,
      worktreeId: body.worktreeId,
    })
    return { ok: true as const }
  }, {
    detail: {
      summary: 'Attach session to existing worktree',
    },
    params: SessionModel.idParams,
    body: SessionModel.isolationAttachBody,
    response: { 200: t.Object({ ok: t.Literal(true) }) },
  })
  .get('/:id/isolation', async ({ params }) => {
    const session = Worktree.getSessionForIsolation(params.id)
    if (!session) {
      throw new AppError({ code: 'session_not_found', status: 404, message: 'Session not found' })
    }
    return await Worktree.readSessionIsolationAsync(session)
  }, {
    detail: {
      summary: 'Get session isolation state',
    },
    params: SessionModel.idParams,
    response: { 200: SessionModel.isolationView },
  })
