import { useEffect, useId, useRef, type CSSProperties, type JSX, type KeyboardEvent } from 'react'
import { Button } from './Button'
import { parseAnsiScreen, type AnsiSegment } from './internal/ansi-screen'

export type TerminalViewState = 'connecting' | 'attached' | 'unavailable' | 'ended'

/** Keys a pane needs that plain text cannot express. Names match the tmux vocabulary
 * the collector accepts, so no translation table exists between the two. */
export type TerminalKey = 'Enter' | 'Escape' | 'Up' | 'Down' | 'C-c'

export interface TerminalInput {
  /** Literal text to type into the session. */
  text?: string
  key?: TerminalKey
}

export interface TerminalViewProps {
  /** Captured screen, ANSI escapes included. null = nothing captured yet. */
  screen: string | null
  state: TerminalViewState
  /** Why the terminal is not attached — required reading for `unavailable`/`ended`. */
  reason?: string
  /** Accessible name, e.g. the session being attached. */
  label: string
  /** Rendered footer note, e.g. capture age. Omitted when absent — never invented. */
  capturedNote?: string
  onSend?(input: TerminalInput): void
  /** True while a send is in flight; disables the composer without hiding it. */
  busy?: boolean
  /** Visible height in text rows. */
  rows?: number
}

const KEY_BUTTONS: { key: TerminalKey; label: string }[] = [
  { key: 'Escape', label: 'Esc' },
  { key: 'Up', label: '↑' },
  { key: 'Down', label: '↓' },
  { key: 'Enter', label: '⏎' },
  { key: 'C-c', label: 'Ctrl-C' },
]

const STATE_NOTE: Record<TerminalViewState, string> = {
  connecting: 'Connecting to the session…',
  attached: '',
  unavailable: 'Not attached.',
  ended: 'Session ended.',
}

function segmentStyle(segment: AnsiSegment): CSSProperties {
  const fg = segment.fg === null ? 'var(--mod-color-term-fg)' : `var(--mod-color-term-ansi-${segment.fg})`
  const bg = segment.bg === null ? 'transparent' : `var(--mod-color-term-ansi-${segment.bg})`
  return {
    color: segment.inverse ? 'var(--mod-color-term-bg)' : fg,
    backgroundColor: segment.inverse ? fg : bg,
    fontWeight: segment.bold ? 700 : undefined,
    fontStyle: segment.italic ? 'italic' : undefined,
    textDecoration: segment.underline ? 'underline' : undefined,
    opacity: segment.dim ? 0.7 : undefined,
  }
}

/**
 * A live terminal pane in the deck: renders a captured screen and hands typed input
 * back to its owner. Transport-agnostic on purpose — it neither fetches nor holds a
 * connection, so the same primitive serves any attach mechanism.
 */
export function TerminalView({
  screen,
  state,
  reason,
  label,
  capturedNote,
  onSend,
  busy = false,
  rows = 24,
}: TerminalViewProps): JSX.Element {
  const outputRef = useRef<HTMLDivElement>(null)
  const inputRef = useRef<HTMLTextAreaElement>(null)
  const inputId = useId()

  useEffect(() => {
    const node = outputRef.current
    if (node) node.scrollTop = node.scrollHeight
  }, [screen])

  const lines = screen === null ? [] : parseAnsiScreen(screen)
  const interactive = state === 'attached' && onSend !== undefined
  const note = STATE_NOTE[state]

  function submitText(): void {
    const node = inputRef.current
    if (!node || !onSend) return
    if (node.value !== '') onSend({ text: node.value })
    onSend({ key: 'Enter' })
    node.value = ''
  }

  function onInputKeyDown(event: KeyboardEvent<HTMLTextAreaElement>): void {
    if (event.key !== 'Enter' || event.shiftKey) return
    event.preventDefault()
    submitText()
  }

  return (
    <div className="flex min-w-0 flex-col gap-2">
      <div
        ref={outputRef}
        role="log"
        aria-label={`${label} terminal output`}
        data-testid="terminal-output"
        data-terminal-state={state}
        tabIndex={0}
        className="overflow-auto rounded-md border border-border p-2 font-mono text-[12px] leading-[1.35] whitespace-pre focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent"
        style={{
          backgroundColor: 'var(--mod-color-term-bg)',
          color: 'var(--mod-color-term-fg)',
          maxHeight: `${rows * 1.35}em`,
        }}
      >
        {lines.length === 0 ? (
          <span className="text-fg-muted">{reason ?? (note === '' ? 'No screen captured yet.' : note)}</span>
        ) : (
          lines.map((segments, lineIndex) => (
            <div key={lineIndex}>
              {segments.length === 0
                ? ' '
                : segments.map((segment, segmentIndex) => (
                  <span key={segmentIndex} style={segmentStyle(segment)}>{segment.text}</span>
                ))}
            </div>
          ))
        )}
      </div>

      {lines.length > 0 && (note !== '' || reason) ? (
        <p className="text-[11px] text-fg-muted">{reason ?? note}</p>
      ) : null}

      <div className="flex flex-wrap items-center gap-1.5">
        <label className="sr-only" htmlFor={inputId}>{`Send input to ${label}`}</label>
        <textarea
          ref={inputRef}
          id={inputId}
          rows={1}
          disabled={!interactive || busy}
          onKeyDown={onInputKeyDown}
          placeholder={interactive ? 'Type here, Enter sends' : 'Input unavailable'}
          className="min-h-8 min-w-0 flex-1 resize-none rounded-md border border-border bg-surface px-2 py-1.5 font-mono text-[12px] text-fg placeholder:text-fg-subtle focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent disabled:cursor-not-allowed disabled:opacity-50"
        />
        <Button size="sm" variant="outline" tone="accent" disabled={!interactive || busy} onClick={submitText}>
          Send
        </Button>
        {KEY_BUTTONS.map((entry) => (
          <Button
            key={entry.key}
            size="sm"
            variant="outline"
            tone={entry.key === 'C-c' ? 'danger' : 'neutral'}
            disabled={!interactive || busy}
            aria-label={`Send ${entry.key}`}
            onClick={() => onSend?.({ key: entry.key })}
          >
            {entry.label}
          </Button>
        ))}
      </div>

      {capturedNote ? <p className="text-[11px] text-fg-subtle tabular-nums">{capturedNote}</p> : null}
    </div>
  )
}
