'use client'

import * as React from 'react'
import { useAgentSession } from '@startsimpli/realtime/react'
import type { AgentLifecycleMsg } from '@startsimpli/realtime'
import { cn } from '../../lib/utils'
import { Terminal, type TerminalHandle } from '../terminal/Terminal'
import type { ChatMessageData } from '../chat/types'
import { ConversationTimeline } from './ConversationTimeline'
import { DiffViewer } from '../diff/DiffViewer'
import { mapSocketStatus } from './status'

export interface AgentSessionConsoleProps {
  /** WebSocket URL for the agent session (ws(s)://…). */
  url: string
  /** Auth token appended by the socket (string or getter re-read on reconnect). */
  token?: string | (() => string | null | undefined)
  /** A raw unified diff to show in the Diff tab (the parent fetches it). */
  diff?: string
  /** Load/refresh the diff (wired to the Diff tab's refresh button). */
  onLoadDiff?: () => void
  diffLoading?: boolean
  diffError?: string | null
  /** Ref/branch the diff is against (shown in the diff header). */
  diffBaseRef?: string
  title?: string
  className?: string
}

type Tab = 'session' | 'diff'

/** Turn a structured lifecycle event into a timeline row. Generic — no app knowledge. */
export function lifecycleToMessage(msg: AgentLifecycleMsg): ChatMessageData {
  const data = msg.data as { message?: string; tool?: string } | undefined
  const looksLikeTool = /tool|command|exec|edit|write|read|bash/i.test(msg.event)
  const content = data?.message ?? msg.event.replace(/[_-]/g, ' ')
  return {
    id: `lifecycle-${msg.ts ?? Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
    role: looksLikeTool ? 'tool' : 'system',
    kind: looksLikeTool ? 'tool' : 'status',
    toolName: looksLikeTool ? (data?.tool ?? msg.event) : undefined,
    content,
    timestamp: msg.ts,
  }
}

/**
 * The live agent-session surface: a status header + a `<Terminal>` and
 * `<ConversationTimeline>` side by side (with a `<DiffViewer>` in a tab). It is
 * driven by `useAgentSession` from `@startsimpli/realtime/react`:
 *   - inbound output is pumped into the terminal imperatively (no re-render/byte),
 *   - keystrokes/resizes are forwarded to `session.send`/`session.resize`,
 *   - lifecycle events accumulate into the conversation timeline,
 *   - the composer steers the agent via `session.send`.
 *
 * Fully generic — no Foundry/app types. The parent supplies the URL, token, and
 * (optionally) a diff.
 */
export function AgentSessionConsole({
  url,
  token,
  diff,
  onLoadDiff,
  diffLoading,
  diffError,
  diffBaseRef,
  title,
  className,
}: AgentSessionConsoleProps) {
  const termHandleRef = React.useRef<TerminalHandle | null>(null)
  const [tab, setTab] = React.useState<Tab>('session')
  const [entries, setEntries] = React.useState<ChatMessageData[]>([])

  const session = useAgentSession({
    url,
    token,
    onOutput: (d) => termHandleRef.current?.write(d),
    onLifecycle: (m) => setEntries((prev) => [...prev, lifecycleToMessage(m)]),
  })

  const { status, lastEvent, send, sendMessage, resize, socket } = session

  // An error event trumps the socket status for the terminal indicator.
  const isError = lastEvent?.type === 'error'
  const terminalStatus = isError ? 'error' : mapSocketStatus(status)

  const exited = lastEvent?.type === 'exited' ? lastEvent : null

  const handleSend = React.useCallback(
    (text: string) => {
      // The composer sends a STEER MESSAGE (submitted as a new turn by the runner), NOT
      // raw keystrokes — otherwise it lands in the agent's prompt box without an Enter and
      // is never processed (bd 768w.16.7.14.9). Terminal typing still uses `send` (onData).
      sendMessage(text)
      setEntries((prev) => [
        ...prev,
        { id: `user-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, role: 'user', content: text },
      ])
    },
    [sendMessage],
  )

  const reconnect = React.useCallback(() => socket?.connect(), [socket])

  const statusLabel = exited
    ? `Exited (code ${exited.exitCode})`
    : isError
      ? 'Error'
      : status === 'open'
        ? 'Connected'
        : status === 'connecting'
          ? 'Connecting…'
          : status === 'reconnecting'
            ? 'Reconnecting…'
            : 'Disconnected'

  return (
    <div className={cn('flex h-full flex-col overflow-hidden rounded-lg border border-border bg-card', className)}>
      {/* Status header + tabs */}
      <div className="flex flex-shrink-0 flex-wrap items-center justify-between gap-2 border-b border-border px-4 py-2">
        <div className="flex items-center gap-3">
          {title && <span className="text-sm font-semibold text-foreground">{title}</span>}
          <span
            data-testid="agent-session-status"
            className={cn(
              'inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium',
              terminalStatus === 'connected' && 'bg-emerald-500/15 text-emerald-600 dark:text-emerald-400',
              terminalStatus === 'connecting' && 'bg-muted text-muted-foreground',
              terminalStatus === 'error' && 'bg-red-500/15 text-red-600 dark:text-red-400',
              terminalStatus === 'disconnected' && 'bg-muted text-muted-foreground',
            )}
          >
            <span className="h-1.5 w-1.5 rounded-full bg-current" />
            {statusLabel}
          </span>
        </div>
        <div className="flex items-center gap-1 rounded-lg bg-muted p-0.5" role="tablist">
          <button
            type="button"
            role="tab"
            aria-selected={tab === 'session'}
            onClick={() => setTab('session')}
            className={cn(
              'rounded-md px-3 py-1 text-xs font-medium transition-colors',
              tab === 'session' ? 'bg-card text-foreground shadow-sm' : 'text-muted-foreground hover:text-foreground',
            )}
          >
            Session
          </button>
          <button
            type="button"
            role="tab"
            aria-selected={tab === 'diff'}
            onClick={() => setTab('diff')}
            className={cn(
              'rounded-md px-3 py-1 text-xs font-medium transition-colors',
              tab === 'diff' ? 'bg-card text-foreground shadow-sm' : 'text-muted-foreground hover:text-foreground',
            )}
          >
            Diff
          </button>
        </div>
      </div>

      <div className="relative min-h-0 flex-1">
        {/* Session tab: Terminal | Conversation split. Terminal stays MOUNTED
            across tab switches (via `hidden`) so xterm is never re-initialised. */}
        <div className={cn('grid h-full grid-cols-1 gap-3 p-3 lg:grid-cols-2', tab !== 'session' && 'hidden')}>
          <Terminal
            onReady={(h) => {
              termHandleRef.current = h
            }}
            onData={send}
            onResize={resize}
            status={terminalStatus}
            onReconnect={reconnect}
            className="min-h-[320px]"
          />
          <ConversationTimeline
            entries={entries}
            title="Conversation"
            statusLabel={statusLabel}
            onSend={handleSend}
            emptyState={<span className="text-sm text-muted-foreground">Steer the agent by sending a message.</span>}
            className="min-h-[320px]"
          />
        </div>

        {/* Diff tab */}
        <div className={cn('h-full p-3', tab !== 'diff' && 'hidden')}>
          <DiffViewer
            diff={diff ?? ''}
            baseRef={diffBaseRef}
            isLoading={diffLoading}
            error={diffError}
            onRefresh={onLoadDiff}
            className="h-full rounded-lg border border-border"
          />
        </div>
      </div>
    </div>
  )
}
