import { appendFileSync, mkdirSync } from 'node:fs'
import { dirname, join } from 'node:path'
import { homedir } from 'node:os'
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import * as z from 'zod'
import { startWsMcpGateway } from './gateway.bundle.mjs'

const stateDir =
  process.env.WS_STATE_DIR ||
  join(homedir(), '.claude', 'channels', 'ws')
const bootLog = join(stateDir, 'boot.log')
mkdirSync(dirname(bootLog), { recursive: true })

function logBoot(line) {
  try {
    appendFileSync(bootLog, `${new Date().toISOString()} ${line}\n`, {
      encoding: 'utf8',
      mode: 0o600,
      flag: 'a',
    })
  } catch {}
}

logBoot(`[BOOT] ws-channel index.mjs pid=${process.pid}`)

const server = new McpServer(
  { name: 'ws-channel', version: '0.1.0' },
  {
    capabilities: {
      experimental: {
        'claude/channel': {},
        'claude/ws_control': {},
      },
      tools: {},
    },
    instructions: [
      'Browser WebSocket clients connect to this channel HTTP gateway.',
      'Inbound prompts arrive via notifications/claude/channel.',
      'Outbound uses ws_push_event and ws_reply tools.',
    ].join('\n'),
  },
)

/** sessionId → push 函数，由 gateway 注册 */
let pushToSession = (_sessionId, _envelope) => {}

server.tool(
  'ws_push_event',
  'Push a v3 protocol envelope to a browser WebSocket session',
  {
    session_id: z.string(),
    envelope: z.record(z.string(), z.unknown()),
  },
  async ({ session_id, envelope }) => {
    pushToSession(session_id, envelope)
    return { content: [{ type: 'text', text: 'ok' }] }
  },
)

server.tool(
  'ws_reply',
  'Send turn-end assistant text to a browser WebSocket session',
  {
    session_id: z.string(),
    text: z.string(),
  },
  async ({ session_id, text }) => {
    pushToSession(session_id, { type: 'assistant', text })
    return { content: [{ type: 'text', text: 'ok' }] }
  },
)

async function emitChannelNotification(content, meta) {
  await server.server.notification({
    method: 'notifications/claude/channel',
    params: { content, meta },
  })
}

async function emitWsControl(params) {
  await server.server.notification({
    method: 'notifications/claude/ws_control',
    params,
  })
}

const rawPort = process.env.WS_PORT?.trim()
const port = rawPort ? Number(rawPort) : 0
const rpcSocket = process.env.WS_MAIN_RPC_SOCKET?.trim() ?? ''

if (Number.isFinite(port) && port > 0 && rpcSocket.length > 0) {
  const gw = startWsMcpGateway({
    port,
    mainRpcSocket: rpcSocket,
    hooks: {
      onPrompt: async ({ agent, sessionId, text, meta }) => {
        await emitChannelNotification(text, {
          ...meta,
          agent,
          channel_type: 'ws',
          ...(sessionId ? { peer_id: sessionId } : {}),
        })
      },
      onControl: async event => {
        if (event.type === 'open') {
          await emitWsControl({
            controlType: 'open',
            agent: event.data.agent,
            sessionId: event.data.sessionId ?? '',
            workspaceDir: event.data.workspaceDir,
            agentEnv: event.data.agentEnv,
            envOverrides: event.data.envOverrides,
            providerOverride: event.data.providerOverride ?? '',
            modelOverride: event.data.modelOverride ?? '',
          })
          return
        }
        if (event.type === 'close') {
          await emitWsControl({
            controlType: 'close',
            agent: event.agent,
            sessionId: event.sessionId ?? '',
          })
          return
        }
        await emitWsControl({
          controlType: 'message',
          agent: event.agent,
          sessionId: event.sessionId ?? '',
          payload: JSON.stringify(event.payload ?? {}),
        })
      },
    },
  })
  pushToSession = gw.pushToSession
  logBoot(`[BOOT] ws gateway listening port=${port}`)
} else {
  logBoot('[BOOT] ws gateway skipped: WS_PORT or WS_MAIN_RPC_SOCKET invalid')
}

const transport = new StdioServerTransport()
await server.connect(transport)
logBoot('[BOOT] mcp stdio connected')
