import { request } from './client'

// AgentClientType mirrors backend acp.ClientType: decides which config-dir
// redirect env var is injected at spawn and which config form is rendered.
export type AgentClientType = 'claude_code' | 'codex' | 'gemini' | 'generic'

// McpServerSpec mirrors backend acp.McpServerSpec. Delivered to the agent via
// ACP session/new alongside the built-in ACW MCP server (per-session token).
// stdio is the ACP baseline; http/sse entries are skipped for agents that do
// not declare the matching mcpCapabilities. Name "acw" is reserved.
export interface McpServerSpec {
  name: string
  type: 'stdio' | 'http' | 'sse'
  command?: string
  args?: string[]
  env?: Record<string, string>
  url?: string
  headers?: Record<string, string>
}

// Admin DTO from `internal/acp/dto.go: AgentKindAdminDTO`.
export interface AgentKindAdmin {
  id: string
  name: string
  display_name: string
  description: string
  binary_path: string
  args: string[]
  env_keys: string[]
  enabled: boolean
  tool_allowlist: string[]
  client_type: AgentClientType
  mcp_servers: McpServerSpec[]
  created_by: string
  created_at: string
  updated_at: string
}

// Public DTO (non-admin view) from `internal/acp/dto.go: AgentKindPublicDTO`.
export interface AgentKindPublic {
  id: string
  name: string
  display_name: string
  enabled: boolean
}

// Type guard distinguishing admin vs public DTO based on presence of admin-only fields.
export function isAgentKindAdmin(k: AgentKindAdmin | AgentKindPublic): k is AgentKindAdmin {
  return 'binary_path' in k
}

export interface CreateAgentKindReq {
  name: string
  display_name: string
  description?: string
  binary_path: string
  args?: string[]
  env?: Record<string, string>
  enabled?: boolean
  tool_allowlist?: string[]
  client_type?: AgentClientType
  mcp_servers?: McpServerSpec[]
}

export interface UpdateAgentKindReq {
  display_name?: string
  description?: string
  binary_path?: string
  args?: string[]
  env?: Record<string, string>
  enabled?: boolean
  tool_allowlist?: string[]
  client_type?: AgentClientType
  mcp_servers?: McpServerSpec[]
}

// AgentConfigFile mirrors backend ConfigFileDTO (admin only, plaintext content).
export interface AgentConfigFile {
  rel_path: string
  content: string
  updated_at: string
}

// PermissionRequest mirrors backend PermissionRequestDTO.
export interface PermissionRequest {
  id: string
  session_id: string
  tool_name: string
  tool_call: unknown
  options: { id: string; name?: string }[]
  status: string
  created_at: string
}

// AcpSession mirrors the backend session DTO returned by /api/v1/acp/sessions.
export interface AcpSession {
  id: string
  workspace_id: string
  project_id: string
  agent_kind_id: string
  user_id: string
  issue_id: string | null
  requirement_id: string | null
  agent_session_id: string | null
  branch: string
  cwd_path: string
  is_main_worktree: boolean
  status: 'starting' | 'running' | 'crashed' | 'exited'
  pid: number | null
  exit_code: number | null
  last_error: string | null
  started_at: string
  ended_at: string | null
}

export interface CreateSessionReq {
  workspace_id: string
  agent_kind_id: string
  branch?: string
  issue_number?: number
  requirement_number?: number
  initial_prompt?: string
}

// AcpEvent is one frame from the session event stream (history REST + WS push).
export interface AcpEvent {
  id: number
  direction: 'in' | 'out'
  kind:
    | 'request'
    | 'response'
    | 'notification'
    | 'error'
    | 'state'
    | 'session_terminated'
    | 'slow_consumer_disconnect'
    | 'client_error'
    | 'permission_request'
    | 'permission_resolved'
  method?: string
  payload: unknown
  truncated: boolean
}

const enc = (v: string) => encodeURIComponent(v)

export const acpApi = {
  agentKinds: {
    list: () => request<(AgentKindAdmin | AgentKindPublic)[]>('/api/v1/acp/agent-kinds'),
    get: (id: string) => request<AgentKindAdmin>(`/api/v1/acp/agent-kinds/${enc(id)}`),
    create: (req: CreateAgentKindReq) =>
      request<AgentKindAdmin>('/api/v1/acp/agent-kinds', { method: 'POST', body: req }),
    update: (id: string, req: UpdateAgentKindReq) =>
      request<AgentKindAdmin>(`/api/v1/acp/agent-kinds/${enc(id)}`, { method: 'PATCH', body: req }),
    remove: (id: string) =>
      request<void>(`/api/v1/acp/agent-kinds/${enc(id)}`, { method: 'DELETE' }),
  },
  // Agent CLI 配置文件（admin only）。rel_path 含斜杠，走 body / query 而非 URL path。
  configFiles: {
    list: (kindId: string) =>
      request<AgentConfigFile[]>(`/api/v1/acp/agent-kinds/${enc(kindId)}/config-files`),
    upsert: (kindId: string, relPath: string, content: string) =>
      request<AgentConfigFile>(`/api/v1/acp/agent-kinds/${enc(kindId)}/config-files`, {
        method: 'PUT',
        body: { rel_path: relPath, content },
      }),
    remove: (kindId: string, relPath: string) =>
      request<void>(
        `/api/v1/acp/agent-kinds/${enc(kindId)}/config-files?rel_path=${enc(relPath)}`,
        { method: 'DELETE' },
      ),
  },
  sessions: {
    list: (opts: { all?: boolean; requirement_id?: string } = {}) => {
      const q = new URLSearchParams()
      if (opts.all) q.set('all', 'true')
      if (opts.requirement_id) q.set('requirement_id', opts.requirement_id)
      const qs = q.toString()
      return request<AcpSession[]>('/api/v1/acp/sessions' + (qs ? '?' + qs : ''))
    },
    get: (id: string) => request<AcpSession>(`/api/v1/acp/sessions/${enc(id)}`),
    create: (req: CreateSessionReq) =>
      request<AcpSession>('/api/v1/acp/sessions', { method: 'POST', body: req }),
    terminate: (id: string) =>
      request<void>(`/api/v1/acp/sessions/${enc(id)}`, { method: 'DELETE' }),
    events: (id: string, since: number) =>
      request<AcpEvent[]>(`/api/v1/acp/sessions/${enc(id)}/events?since=${since}`),
    permissions: (id: string) =>
      request<PermissionRequest[]>(`/api/v1/acp/sessions/${enc(id)}/permissions`),
  },
  permissions: {
    approve: (reqID: string, optionId?: string) =>
      request<void>(`/api/v1/acp/permissions/${enc(reqID)}/approve`, {
        method: 'POST',
        body: optionId ? { option_id: optionId } : {},
      }),
    deny: (reqID: string) =>
      request<void>(`/api/v1/acp/permissions/${enc(reqID)}/deny`, { method: 'POST' }),
  },
}

// openSessionWS opens a native WebSocket to /sessions/{id}/ws.
//
// Auth: browser WebSocket cannot send custom headers, so the token is sent
// via the Sec-WebSocket-Protocol handshake as "acw-token.<token>". This is
// safer than a URL query parameter (which may be logged by proxies). The
// application subprotocol "acp-v1" is also advertised and selected by the
// server when supported.
export function openSessionWS(sessionId: string, since: number, token: string): WebSocket {
  const proto = window.location.protocol === 'https:' ? 'wss' : 'ws'
  const host = window.location.host
  const url = `${proto}://${host}/api/v1/acp/sessions/${enc(sessionId)}/ws?since=${since}`
  const protocols = ['acp-v1']
  if (token) protocols.push(`acw-token.${token}`)
  return new WebSocket(url, protocols)
}
