/**
 * RunMonitor — top-level composite for live training-run monitoring.
 * @countermeasure-platform/web-components/composites/run-monitor
 *
 * Renders a flexible run-monitor layout. All slots are optional and absent
 * slots collapse cleanly. The default ordering is:
 *
 *   header        → top of the card (consumer renders back-button, title)
 *   PhaseProgress → always (driven by `phases` + `currentPhase` + `status`)
 *   MetricsCard   → when `metrics` is provided
 *   PerCaseStats  → when `perCaseStats` + `perCaseColumns` are provided
 *   LiveLogTail   → always (auto-following CodeMirror viewer)
 *   summary       → after the per-case table when present
 *   error         → red-tinted card when `status === 'failed' | 'cancelled'`
 *   actions       → bottom-right (consumer renders cancel/resume buttons)
 *
 * SSE wiring is opt-in via `streamSource`:
 *   - `'auto'` (default): the composite calls `useSSE(streamUrl)` itself,
 *     dispatches `log` events into the LogTail, and exposes connection
 *     state via the live badge.
 *   - `'none'`: the composite does NOT open an EventSource. Callers feed
 *     log lines via the `onLogLine` callback (which they invoke from their
 *     own SSE handler) — useful when the host already manages SSE for
 *     phase / progress / status events outside of this composite.
 *
 * `lineParser` colours each line; `onLogLine` fires for every line so a
 * host can update per-case stats from log content without re-implementing
 * the LogTail buffer.
 */

import * as React from 'react'
import {
  PhaseProgress,
  type PhaseProgressPhase,
  type PhaseProgressStatus,
} from '../primitives/phase-progress'
import { SSELiveBadge } from '../primitives/sse-live-badge'
import {
  LiveLogTail,
  type LiveLogTailHandle,
  type LiveLogTailParseResult,
} from '../primitives/live-log-tail'
import { useSSE } from '../services/sse-react'
import { Button } from '../react/primitives/button'
import { cn } from '../react/primitives/utils'
import { MetricsCard, type MetricsItem } from './metrics-card'
import { PerCaseStatsTable, type PerCaseColumnDef, type PerCaseRow } from './per-case-stats-table'

export type { PhaseProgressPhase as RunMonitorPhase, PhaseProgressStatus as RunMonitorStatus }
export type { MetricsItem, PerCaseColumnDef, PerCaseRow }

export type RunMonitorStreamSource = 'auto' | 'none'

export interface RunMonitorProps extends React.HTMLAttributes<HTMLDivElement> {
  runId: number | string
  streamUrl: string
  phases: PhaseProgressPhase[]
  currentPhase: string
  status: PhaseProgressStatus
  metrics?: MetricsItem[]
  perCaseStats?: PerCaseRow[]
  perCaseColumns?: PerCaseColumnDef[]
  /**
   * Auth token for the SSE connection. NOT surfaced in the DOM — intentionally
   * stripped to prevent token leakage. Cookie-backed auth (`withCredentials`)
   * is the supported path; query-string tokens require the lower-level
   * SSEManager with explicit `allowQueryTokenAuth`.
   */
  sseToken?: string

  /* -------- New in v0.4 — slots & log handling ----------------------- */

  /** Slot rendered in the top-left of the header strip. */
  header?: React.ReactNode
  /** Slot rendered in the top-right of the header strip. */
  actions?: React.ReactNode
  /** Slot rendered after the per-case table. */
  summary?: React.ReactNode
  /**
   * Slot rendered when `status === 'failed' | 'cancelled'`. Wrapped in a
   * red-tinted card so the host can pass plain text or a structured node.
   */
  error?: React.ReactNode
  /** Custom log-line parser used by `LiveLogTail` for severity coloring. */
  lineParser?: (line: string) => LiveLogTailParseResult
  /**
   * Fires for every parsed log line — including ones synthesized by the
   * host when `streamSource='none'`. Hosts use this to drive per-case
   * stats from log content without re-implementing the LogTail buffer.
   */
  onLogLine?: (line: string) => void
  /**
   * Whether this composite manages its own SSE connection. Defaults to
   * `'auto'`. `'none'` disables internal SSE wiring so callers can feed
   * the LogTail externally via the `logRef` ref.
   */
  streamSource?: RunMonitorStreamSource
  /**
   * Imperative handle into the embedded LogTail. Hosts using
   * `streamSource='none'` call `logRef.current?.append(line)` from their
   * own event handler.
   */
  logRef?: React.Ref<LiveLogTailHandle>

  /* -------- Deprecated props kept for v0.3.0 compatibility ----------- */

  /**
   * @deprecated Pass a Cancel button via the `actions` slot. Kept for
   * back-compat with v0.3.0 callers.
   */
  onCancel?: () => void
  /**
   * @deprecated Pass a Resume button via the `actions` slot. Kept for
   * back-compat with v0.3.0 callers.
   */
  onResume?: () => void
  /**
   * @deprecated Renamed to `lineParser` to mirror `LiveLogTail`. Both
   * names are accepted; `lineParser` wins when both are supplied.
   */
  logLineParser?: (line: string) => LiveLogTailParseResult
}

function extractLogLine(payload: unknown): string | null {
  if (typeof payload === 'string') return payload
  if (payload && typeof payload === 'object') {
    const obj = payload as Record<string, unknown>
    if (typeof obj.line === 'string') return obj.line
    if (typeof obj.message === 'string') return obj.message
    if (typeof obj.text === 'string') return obj.text
  }
  return null
}

/**
 * Internal: only mounts when `streamSource === 'auto'`. Hooks must be
 * called unconditionally inside their owning component, so we extract the
 * `useSSE` call into a sibling that's only rendered in auto mode. The
 * composite passes `connected` back via the `onConnected` callback.
 */
function InternalSSEWiring({
  url,
  onLine,
  onConnected,
}: {
  url: string
  onLine: (line: string) => void
  onConnected: (connected: boolean) => void
}) {
  const onLineRef = React.useRef(onLine)
  const onConnectedRef = React.useRef(onConnected)
  React.useEffect(() => {
    onLineRef.current = onLine
  }, [onLine])
  React.useEffect(() => {
    onConnectedRef.current = onConnected
  }, [onConnected])

  const sseEvents = React.useMemo<Record<string, (data: unknown) => void>>(
    () => ({
      log: (payload: unknown) => {
        const line = extractLogLine(payload)
        if (line !== null) {
          onLineRef.current(line)
        }
      },
    }),
    []
  )

  const { connected } = useSSE({
    url,
    withCredentials: true,
    events: sseEvents,
  })

  React.useEffect(() => {
    onConnectedRef.current(connected)
  }, [connected])

  return null
}

export function RunMonitor({
  runId,
  streamUrl,
  phases,
  currentPhase,
  status,
  metrics,
  perCaseStats,
  perCaseColumns,
  // Accepted for API compatibility but intentionally not forwarded to the
  // DOM — placing the token on a `data-*` attribute leaks it to anything
  // walking the DOM tree (extensions, analytics scripts, XSS). The token
  // belongs in JS-land (cookies / Authorization headers / query params on
  // the EventSource URL the host constructs); see run-monitor-token-exposure
  // test for the regression guard.
  sseToken: _sseToken,
  header,
  actions,
  summary,
  error,
  lineParser,
  logLineParser,
  onLogLine,
  streamSource = 'auto',
  logRef,
  onCancel,
  onResume,
  className,
  ...rest
}: RunMonitorProps) {
  const internalLogRef = React.useRef<LiveLogTailHandle>(null)
  // Bridge between consumer-provided `logRef` and the LogTail mount.
  const logTailRef = React.useCallback(
    (instance: LiveLogTailHandle | null) => {
      internalLogRef.current = instance
      if (typeof logRef === 'function') {
        logRef(instance)
      } else if (logRef && typeof logRef === 'object') {
        ;(logRef as React.MutableRefObject<LiveLogTailHandle | null>).current = instance
      }
    },
    [logRef]
  )

  const [connected, setConnected] = React.useState(false)
  const sseEnabled = streamSource === 'auto'

  // Stable callback passed to the internal SSE wiring; appends to the
  // LogTail and forwards each line to the host's `onLogLine`.
  const handleSSELine = React.useCallback(
    (line: string) => {
      internalLogRef.current?.append(line)
      onLogLine?.(line)
    },
    [onLogLine]
  )

  const isRunning = status === 'running'
  const isFailedOrCancelled = status === 'failed' || status === 'cancelled'

  // Default header layout — the live badge is always present so the user
  // can tell whether the stream is open even when the host supplies a
  // custom header slot.
  const headerNode = (
    <div className="flex items-center justify-between gap-3">
      <div className="flex flex-1 items-center gap-3 min-w-0">{header}</div>
      <div className="flex shrink-0 items-center gap-2">
        {sseEnabled && <SSELiveBadge connected={connected} />}
        {actions}
        {/* v0.3 back-compat — only render when host hasn't supplied
            its own actions slot. */}
        {!actions && onCancel && isRunning && (
          <Button tone="shadcn" variant="outline" size="sm" onClick={onCancel}>
            Cancel
          </Button>
        )}
        {!actions && onResume && isFailedOrCancelled && (
          <Button tone="shadcn" variant="default" size="sm" onClick={onResume}>
            Resume
          </Button>
        )}
      </div>
    </div>
  )

  const effectiveLineParser = lineParser ?? logLineParser

  return (
    <div
      data-slot="run-monitor"
      data-run-id={String(runId)}
      data-stream-source={streamSource}
      className={cn('flex w-full flex-col gap-4', className)}
      {...rest}
    >
      {sseEnabled && (
        <InternalSSEWiring url={streamUrl} onLine={handleSSELine} onConnected={setConnected} />
      )}
      {headerNode}

      <PhaseProgress phases={phases} currentPhase={currentPhase} status={status} />

      {metrics && metrics.length > 0 && <MetricsCard items={metrics} />}

      {perCaseStats && perCaseColumns && (
        <PerCaseStatsTable
          rows={perCaseStats}
          columnDefs={perCaseColumns}
          {...(currentPhase ? { currentId: currentPhase } : {})}
        />
      )}

      <LiveLogTail
        ref={logTailRef}
        follow
        className="h-[320px]"
        {...(effectiveLineParser ? { lineParser: effectiveLineParser } : {})}
      />

      {summary && <div data-slot="run-monitor-summary">{summary}</div>}

      {isFailedOrCancelled && error && (
        <div
          data-slot="run-monitor-error"
          className="rounded-xl border border-accent-rose/40 bg-accent-rose/5 p-4 text-sm text-accent-rose"
          role="alert"
        >
          {error}
        </div>
      )}
    </div>
  )
}
