'use client'

import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import dynamic from 'next/dynamic'

import { Button, Notice, Segmented, Select } from '@/components/ui/controls'
import { Badge, EmptyState, Panel, PanelBody, PanelHeader, Skeleton, StatCard } from '@/components/ui/display'
import { FallbackNotice, LiveGrid2D } from '@/components/live-grid/live-grid-2d'
import type { CameraSync, CameraSyncState } from '@/components/live-grid/live-grid-scene'
import type { GridLayer, SceneAsset, SceneEdge, SceneNode } from '@/components/live-grid/types'
import { useI18n } from '@/components/providers/i18n-provider'
import { api } from '@/lib/api/client'
import { cn } from '@/lib/cn'

/**
 * Split future and three futures (5.0 §11–§12).
 *
 * Two or three panes of the same grid, at the same instant, under different decisions.
 * Three things make this a comparison rather than two pictures side by side:
 *
 * **One camera.** Orbit, zoom or focus either pane and the others follow. Two scenes seen
 * from two viewpoints compare the viewpoints, not the futures.
 *
 * **One instant, one solver.** Every pane comes from the same server response, solved from
 * the same snapshot with the same stress applied. The client never subtracts two payloads
 * to produce a difference — the differences arrive computed (§34).
 *
 * **Nothing is applied.** Every pane is labelled simulated, and the label comes from the
 * payload rather than from this component's opinion of what it is showing.
 */

// Code-split, exactly as the live console does: a viewer on the 2D fallback must never pay
// to download three.js.
const LiveGridScene = dynamic(() => import('@/components/live-grid/live-grid-scene'), {
  ssr: false,
  loading: () => <Skeleton className="h-full w-full" />,
})

// ─────────────────────────────────────────────────────────────────────────────
// The payload, as it crosses the boundary
// ─────────────────────────────────────────────────────────────────────────────

interface CompareEdge extends Omit<SceneEdge, 'direction'> {
  direction: string
}

interface Kpis {
  stabilityIndex: number
  totalLoadMw: number
  assetsAtRisk: number
  peakRisk: number
  dataConfidencePct: number
}

interface FutureScene {
  key: 'none' | 'recommended' | 'alternative'
  available: boolean
  unavailableReason: string | null
  kind: string
  strategy: {
    key: string
    label: string
    labelAr: string
    rank: number
    actions: Array<{ type: string; assetCode: string; magnitudeMw: number }>
    costSar: number
    executionMin: number
    confidence: number
  } | null
  assets: SceneAsset[]
  edges: CompareEdge[]
  kpis: Kpis
  bottlenecks: string[]
  totalLossMw: number
}

interface FutureDifference {
  futureKey: string
  identical: boolean
  peakRiskDelta: number
  stabilityDelta: number
  totalLoadDelta: number
  assetsAtRiskDelta: number
  lossDelta: number
  changedCount: number
  assets: Array<{
    code: string
    name: string
    nameAr: string
    loadPctBefore: number
    loadPctAfter: number
    tempCBefore: number
    tempCAfter: number
    riskBefore: number
    riskAfter: number
    stateBefore: string
    stateAfter: string
    riskDelta: number
  }>
}

interface CompareResult {
  twinCode: string
  twinName: string
  twinNameAr: string
  assetCode: string | null
  scenario: string | null
  ts: number
  nodes: SceneNode[]
  radius: number
  kind: string
  futures: FutureScene[]
  differences: FutureDifference[]
  note: string
  noteAr: string
}

// ─────────────────────────────────────────────────────────────────────────────
// Helpers
// ─────────────────────────────────────────────────────────────────────────────

/** The scene's direction union, narrowed once here rather than cast at each use. */
function toSceneEdge(edge: CompareEdge): SceneEdge {
  return {
    ...edge,
    direction: edge.direction === 'reverse' ? 'reverse' : edge.direction === 'idle' ? 'idle' : 'forward',
  }
}

function detectWebGL(): boolean {
  try {
    const canvas = document.createElement('canvas')
    const context =
      canvas.getContext('webgl2') ??
      canvas.getContext('webgl') ??
      canvas.getContext('experimental-webgl')
    if (!context) return false
    ;(context as WebGLRenderingContext).getExtension('WEBGL_lose_context')?.loseContext()
    return true
  } catch {
    return false
  }
}

const LAYERS: GridLayer[] = ['risk', 'load', 'temperature', 'power_flow', 'voltage']

/** `n` takes format options, not a digit count; this keeps the call sites readable. */
function fixed(digits: number): Intl.NumberFormatOptions {
  return { minimumFractionDigits: digits, maximumFractionDigits: digits }
}

/** Signed, with the sign kept: a risk that rose and a risk that fell are not one number. */
function signed(value: number, digits = 1): string {
  const rounded = Number(value.toFixed(digits))
  return `${rounded > 0 ? '+' : ''}${rounded}`
}

/** The class for a signed delta in running text. */
function deltaClass(value: number, lowerIsBetter = true): string {
  if (Math.abs(value) < 0.05) return 'text-muted'
  return (lowerIsBetter ? value < 0 : value > 0) ? 'text-normal' : 'text-warning'
}

/** The same judgement as a grid state, for the stat cards. Unmoved gets no colour at all. */
function deltaState(value: number, lowerIsBetter = true): 'normal' | 'warning' | undefined {
  if (Math.abs(value) < 0.05) return undefined
  return (lowerIsBetter ? value < 0 : value > 0) ? 'normal' : 'warning'
}

// ─────────────────────────────────────────────────────────────────────────────
// One pane
// ─────────────────────────────────────────────────────────────────────────────

function FuturePane({
  future,
  title,
  nodes,
  radius,
  layer,
  webgl,
  reducedMotion,
  sync,
  selected,
  onSelect,
}: {
  future: FutureScene
  title: string
  nodes: SceneNode[]
  radius: number
  layer: GridLayer
  webgl: boolean | null
  reducedMotion: boolean
  sync: CameraSync
  selected: string | null
  onSelect: (code: string) => void
}) {
  const { t, n, mw } = useI18n()
  const edges = useMemo(() => future.edges.map(toSceneEdge), [future.edges])

  return (
    <div className="space-y-2" data-testid="compare-pane" data-future={future.key} data-available={future.available}>
      <div className="flex items-center justify-between gap-2">
        <h3 className="text-[13px] font-semibold text-fg">{title}</h3>
        <Badge tone={future.key === 'none' ? 'muted' : 'info'}>{t('common.simulated')}</Badge>
      </div>

      {!future.available ? (
        <div
          className="flex h-[38vh] min-h-[260px] items-center justify-center rounded-[--radius-panel] border border-border bg-surface-2 p-4"
          data-testid="compare-unavailable"
        >
          <EmptyState title={t('compare.unavailable')} body={future.unavailableReason ?? ''} />
        </div>
      ) : (
        <div
          className="relative h-[38vh] min-h-[260px] overflow-hidden rounded-[--radius-panel] border border-border bg-[--color-scene]"
          data-testid="compare-scene"
          data-renderer={webgl ? '3d' : '2d'}
        >
          {webgl === null ? (
            <Skeleton className="h-full w-full" />
          ) : webgl ? (
            <LiveGridScene
              nodes={nodes}
              edges={edges}
              assets={future.assets}
              layer={layer}
              radius={radius}
              selectedCode={selected}
              highlight={null}
              showLabels={false}
              reducedMotion={reducedMotion}
              quality="balanced"
              cameraTarget={null}
              sync={sync}
              onSelect={onSelect}
            />
          ) : (
            <LiveGrid2D
              nodes={nodes}
              edges={edges}
              assets={future.assets}
              layer={layer}
              selectedCode={selected}
              highlight={null}
              showLabels={false}
              reducedMotion={reducedMotion}
              onSelect={onSelect}
            />
          )}
        </div>
      )}

      <div className="grid grid-cols-2 gap-2 sm:grid-cols-4">
        <StatCard label={t('compare.peakRisk')} value={n(future.kpis.peakRisk, fixed(1))} />
        <StatCard label={t('compare.stability')} value={n(future.kpis.stabilityIndex, fixed(1))} />
        <StatCard label={t('compare.load')} value={mw(future.kpis.totalLoadMw)} />
        <StatCard label={t('compare.assetsAtRisk')} value={n(future.kpis.assetsAtRisk, fixed(0))} />
      </div>

      {future.strategy ? (
        <div className="rounded-[--radius-panel] border border-border bg-surface-2 p-3 text-[11px] leading-relaxed">
          <div className="font-medium text-fg">{future.strategy.label}</div>
          <ul className="mt-1 space-y-0.5 text-muted">
            {future.strategy.actions.map((action, index) => (
              <li key={`${action.assetCode}-${action.type}-${index}`}>
                {action.type} · {action.assetCode} · {mw(action.magnitudeMw)}
              </li>
            ))}
          </ul>
          <div className="mt-1 text-muted">
            {t('compare.cost')} {n(future.strategy.costSar, fixed(0))} SAR · {t('compare.executionMin')}{' '}
            {n(future.strategy.executionMin, fixed(0))} min · {t('compare.confidence')}{' '}
            {n(future.strategy.confidence, fixed(1))}
          </div>
        </div>
      ) : future.key === 'none' ? (
        <p className="text-[11px] text-muted">{t('compare.baselineLabel')}</p>
      ) : null}
    </div>
  )
}

// ─────────────────────────────────────────────────────────────────────────────
// The surface
// ─────────────────────────────────────────────────────────────────────────────

export function CompareFutures({
  twins,
  scenarios,
  initialTwin,
  canSimulate,
}: {
  twins: Array<{ code: string; name: string; nameAr: string }>
  scenarios: Array<{ key: string; label: string; labelAr: string }>
  initialTwin: string
  canSimulate: boolean
}) {
  const { t, locale, n, mw } = useI18n()
  const ar = locale === 'ar'

  const [twin, setTwin] = useState(initialTwin)
  const [scenario, setScenario] = useState('normal')
  const [layer, setLayer] = useState<GridLayer>('risk')
  const [third, setThird] = useState(false)
  const [result, setResult] = useState<CompareResult | null>(null)
  const [selected, setSelected] = useState<string | null>(null)
  const [busy, setBusy] = useState(false)
  const [error, setError] = useState<string | null>(null)
  const [webgl, setWebgl] = useState<boolean | null>(null)
  const [reducedMotion, setReducedMotion] = useState(false)

  useEffect(() => {
    setWebgl(detectWebGL())
    const query = window.matchMedia('(prefers-reduced-motion: reduce)')
    setReducedMotion(query.matches)
    const listener = (event: MediaQueryListEvent) => setReducedMotion(event.matches)
    query.addEventListener('change', listener)
    return () => query.removeEventListener('change', listener)
  }, [])

  // The camera every pane shares. A ref rather than state: it is written on every frame a
  // pointer is down, and a re-render per frame would make the follower lag the leader.
  const bus = useRef<CameraSyncState>({
    position: [0, 0, 0],
    target: [0, 0, 0],
    owner: '',
    version: 0,
  })

  const run = useCallback(async () => {
    setBusy(true)
    setError(null)
    try {
      const payload = await api.post<CompareResult>('/api/compare/futures', {
        twinCode: twin,
        scenario: scenario === 'normal' ? undefined : scenario,
        includeAlternative: third,
        demoClock: true,
      })
      setResult(payload)
      setSelected(payload.assetCode)
    } catch (caught) {
      setError(caught instanceof Error ? caught.message : 'Request failed')
    } finally {
      setBusy(false)
    }
  }, [twin, scenario, third])

  const titleFor = (future: FutureScene) =>
    future.key === 'none'
      ? t('compare.left')
      : future.key === 'recommended'
        ? t('compare.right')
        : t('compare.third')

  const differenceFor = (key: string) => result?.differences.find((entry) => entry.futureKey === key) ?? null

  return (
    <div className="space-y-4" data-testid="compare-futures">
      {/* ── Controls ─────────────────────────────────────────────────────── */}
      <Panel>
        <PanelHeader title={t('compare.title')} subtitle={t('compare.sameInstant')} />
        <PanelBody className="space-y-3">
          <div className="flex flex-wrap items-end gap-2">
            <Select
              value={twin}
              aria-label={t('common.region')}
              onChange={(event) => setTwin(event.target.value)}
            >
              {twins.map((entry) => (
                <option key={entry.code} value={entry.code}>
                  {ar ? entry.nameAr : entry.name}
                </option>
              ))}
            </Select>
            <Select
              value={scenario}
              aria-label={t('compare.scenario')}
              onChange={(event) => setScenario(event.target.value)}
            >
              {scenarios.map((entry) => (
                <option key={entry.key} value={entry.key}>
                  {ar ? entry.labelAr : entry.label}
                </option>
              ))}
            </Select>
            <Segmented
              value={layer}
              onChange={(value) => setLayer(value)}
              ariaLabel={t('compare.layer')}
              size="sm"
              options={LAYERS.map((key) => ({ value: key, label: t(`liveGrid.layer.${key}`) }))}
            />
            <Button
              size="sm"
              variant={third ? 'primary' : 'ghost'}
              onClick={() => setThird((value) => !value)}
              title={t('compare.thirdNote')}
            >
              {t('compare.showThird')}
            </Button>
            <Button size="sm" onClick={run} disabled={busy || !canSimulate} data-testid="compare-run">
              {busy ? t('compare.running') : t('compare.run')}
            </Button>
          </div>

          {!canSimulate ? <Notice tone="warning">{t('common.permissionDenied')}</Notice> : null}
          {error ? <Notice tone="danger">{error}</Notice> : null}
          {webgl === false ? <FallbackNotice reason={t('compare.webgl')} /> : null}
          <Notice tone="info">{t('compare.simulated')}</Notice>
          <p className="text-[11px] text-muted">{t('compare.syncNote')}</p>
        </PanelBody>
      </Panel>

      {/* ── The futures ──────────────────────────────────────────────────── */}
      {!result ? (
        <EmptyState title={t('compare.title')} body={t('compare.empty')} />
      ) : (
        <>
          <div
            className={cn(
              'grid gap-4',
              result.futures.length > 2 ? 'xl:grid-cols-3' : 'lg:grid-cols-2',
            )}
          >
            {result.futures.map((future) => (
              <FuturePane
                key={future.key}
                future={future}
                title={titleFor(future)}
                nodes={result.nodes}
                radius={result.radius}
                layer={layer}
                webgl={webgl}
                reducedMotion={reducedMotion}
                sync={{ id: future.key, bus }}
                selected={selected}
                onSelect={setSelected}
              />
            ))}
          </div>

          {/* ── Differences ────────────────────────────────────────────── */}
          {result.differences.map((difference) => (
            <div key={difference.futureKey} data-testid="compare-difference">
            <Panel>
              <PanelHeader
                title={`${t('compare.difference')} · ${
                  difference.futureKey === 'recommended' ? t('compare.right') : t('compare.third')
                }`}
                subtitle={ar ? result.noteAr : result.note}
              />
              <PanelBody className="space-y-3">
                <div className="grid grid-cols-2 gap-2 sm:grid-cols-5">
                  <StatCard
                    label={t('compare.peakRisk')}
                    value={signed(difference.peakRiskDelta)}
                    state={deltaState(difference.peakRiskDelta)}
                  />
                  <StatCard
                    label={t('compare.stability')}
                    value={signed(difference.stabilityDelta)}
                    state={deltaState(difference.stabilityDelta, false)}
                  />
                  <StatCard label={t('compare.load')} value={`${signed(difference.totalLoadDelta)} MW`} />
                  <StatCard
                    label={t('compare.assetsAtRisk')}
                    value={signed(difference.assetsAtRiskDelta, 0)}
                    state={deltaState(difference.assetsAtRiskDelta)}
                  />
                  <StatCard
                    label={t('compare.losses')}
                    value={`${signed(difference.lossDelta, 3)} MW`}
                    state={deltaState(difference.lossDelta)}
                  />
                </div>

                {difference.identical ? (
                  <div data-testid="compare-identical">
                    <Notice tone="info">{t('compare.noDifference')}</Notice>
                  </div>
                ) : (
                  <div className="overflow-x-auto">
                    <table className="w-full text-[11px]">
                      <caption className="sr-only">{t('compare.table')}</caption>
                      <thead className="text-muted">
                        <tr className="text-start">
                          <th className="py-1 text-start">{t('common.asset')}</th>
                          <th className="py-1 text-start">{t('compare.load')}</th>
                          <th className="py-1 text-start">{t('liveGrid.layer.temperature')}</th>
                          <th className="py-1 text-start">{t('compare.riskDelta')}</th>
                          <th className="py-1 text-start">{t('common.status')}</th>
                        </tr>
                      </thead>
                      <tbody className="tabular-nums">
                        {difference.assets.map((asset) => (
                          <tr key={asset.code} className="border-t border-border/60">
                            <td className="py-1">
                              <span className="font-medium text-fg">{asset.code}</span>{' '}
                              <span className="text-muted">{ar ? asset.nameAr : asset.name}</span>
                            </td>
                            <td className="py-1 text-muted">
                              {n(asset.loadPctBefore, fixed(1))} → {n(asset.loadPctAfter, fixed(1))} %
                            </td>
                            <td className="py-1 text-muted">
                              {n(asset.tempCBefore, fixed(1))} → {n(asset.tempCAfter, fixed(1))} °C
                            </td>
                            <td className={cn('py-1', deltaClass(asset.riskDelta))}>
                              {n(asset.riskBefore, fixed(1))} → {n(asset.riskAfter, fixed(1))} ({signed(asset.riskDelta)})
                            </td>
                            <td className="py-1 text-muted">
                              {asset.stateBefore === asset.stateAfter
                                ? t(`states.${asset.stateAfter}`)
                                : `${t(`states.${asset.stateBefore}`)} → ${t(`states.${asset.stateAfter}`)}`}
                            </td>
                          </tr>
                        ))}
                      </tbody>
                    </table>
                    {difference.changedCount > difference.assets.length ? (
                      <p className="pt-2 text-[11px] text-muted">
                        {t('compare.changedOf')
                          .replace('{shown}', String(difference.assets.length))
                          .replace('{total}', String(difference.changedCount))}
                      </p>
                    ) : null}
                  </div>
                )}
              </PanelBody>
            </Panel>
            </div>
          ))}

          {/* The baseline pane has no difference row of its own; this keeps the count honest. */}
          {result.differences.length === 0 ? (
            <Notice tone="warning">{differenceFor('recommended') ? '' : t('compare.noStrategy')}</Notice>
          ) : null}

          <p className="text-[11px] text-muted">
            {ar ? result.twinNameAr : result.twinName} ·{' '}
            {mw(result.futures[0]?.kpis.totalLoadMw ?? 0)} · {t('compare.simulated')}
          </p>
        </>
      )}
    </div>
  )
}
