'use client'

import { useCallback, useEffect, useMemo, useState } from 'react'
import { useRouter, useSearchParams } from 'next/navigation'

import { useI18n } from '@/components/providers/i18n-provider'
import { useAppMode } from '@/components/providers/app-mode-provider'
import { Button, Notice, Segmented } from '@/components/ui/controls'
import { Badge, Panel, PanelBody, PanelHeader, StatCard } from '@/components/ui/display'
import { api, ApiClientError } from '@/lib/api/client'
import { cn } from '@/lib/cn'
import { TwinCanvas } from './twin-canvas'
import { TwinInspector } from './twin-inspector'
import { TwinTimeline, type TimelineFrame } from './twin-timeline'
import { FidelityPanel, type DeviationView, type FidelityView } from './fidelity-panel'
import { GhostFuture } from './ghost-future'
import { SplitFuture, type SplitFrame } from './split-future'
import { ScenarioCanvas, type PresetScenario, type ScenarioEvent } from './scenario-canvas'
import { SimulationProgress } from './simulation-progress'
import type { SceneAssetState, SceneEdge, SceneNode, TwinLayers, TwinView } from './twin-scene-2d'

/**
 * The digital twin workbench (§13–§28, §35–§37).
 *
 * One surface that holds the scene, the timeline, the inspector and the scenario canvas,
 * because they are one activity: look at the grid, move it through time, poke it, and
 * read what happened. Splitting them across pages would make the operator carry state in
 * their head between screens.
 *
 * The mode is declared as `sandbox` while this page is open, which drives the banner and
 * every provenance label beneath it (§6).
 */

export interface TwinSummary {
  code: string
  level: string
  name: string
  nameAr: string
  regionCode: string | null
  assetCode: string | null
  mode: string
  autonomyLevel: number
}

export interface TwinStatePayload {
  twin: TwinSummary
  ts: number
  source: string
  snapshot: {
    totalLoadMw: number
    stabilityIndex: number
    frequencyHz: number
    peakRisk: number
    ambientC: number
    renewableMw: number
    assetsAtRisk: number
  }
  fidelity: FidelityView
  assets: Array<
    SceneAssetState & {
      name: string
      nameAr: string
      assetTypeKey: string
      capacityMw: number
      customersServed: number
      healthScore: number
      voltageKv: number
      voltageDeviationPct: number
      currentA: number
      powerFactor: number
      thdPct: number
      ambientC: number
    }
  >
  scene: { nodes: SceneNode[]; edges: SceneEdge[]; radius: number; level: string; focusCode: string | null }
}

interface CascadeStep {
  assetCode: string
  offsetMin: number
  outcome: string
  probability: number
  mechanism: string
  loadPctAfter: number
}

const VIEWS: TwinView[] = ['standard', 'xray', 'thermal', 'risk']

const LAYER_KEYS = ['assets', 'flow', 'risk', 'sensors', 'cascade', 'labels'] as const

export function TwinWorkbench({
  twins,
  initial,
  deviations,
  presets,
  regions,
  simulations,
  snapshots,
  canSimulate,
}: {
  twins: TwinSummary[]
  initial: TwinStatePayload
  deviations: DeviationView[]
  presets: PresetScenario[]
  regions: Array<{ code: string; name: string; nameAr: string }>
  simulations: Array<{ code: string; createdAt: number; riskBefore: number; riskAfter: number; summary: string; summaryAr: string }>
  snapshots: Array<{ code: string; ts: number; reason: string; peakRisk: number }>
  canSimulate: boolean
}) {
  const { t, locale, n } = useI18n()
  const ar = locale === 'ar'
  const router = useRouter()
  const params = useSearchParams()
  const { setMode } = useAppMode()

  const [state, setState] = useState(initial)
  const [view, setView] = useState<TwinView>('standard')
  const [layers, setLayers] = useState<TwinLayers>({
    assets: true,
    flow: true,
    risk: true,
    sensors: false,
    cascade: true,
    labels: true,
  })
  const [selected, setSelected] = useState<string | null>(
    initial.twin.assetCode ?? initial.assets[0]?.code ?? null,
  )

  const [frames, setFrames] = useState<TimelineFrame[]>([])
  const [frameIndex, setFrameIndex] = useState(0)
  const [futureAssets, setFutureAssets] = useState<Record<number, SceneAssetState[]>>({})

  const [running, setRunning] = useState(false)
  const [error, setError] = useState<string | null>(null)
  const [simulation, setSimulation] = useState<{
    code: string
    riskBefore: number
    riskAfter: number
    summary: string
    summaryAr: string
    snapshotCode: string | null
    frames: TimelineFrame[]
  } | null>(null)
  const [cascade, setCascade] = useState<CascadeStep[]>([])
  const [ghost, setGhost] = useState<{
    present: SceneAssetState & { healthScore: number }
    future: SceneAssetState & { healthScore: number }
    offsetMin: number
    etaMinutes: number | null
  } | null>(null)
  const [split, setSplit] = useState<{ without: SplitFrame[]; withNabdh: SplitFrame[] } | null>(null)

  // The twin page is a sandbox: say so, in the shell, for as long as it is open.
  useEffect(() => {
    setMode('sandbox')
    return () => setMode('live')
  }, [setMode])

  // A scenario handed over by the Copilot arrives in the query string.
  const handoverEvents = useMemo<ScenarioEvent[] | undefined>(() => {
    const raw = params.get('events')
    if (!raw) return undefined
    try {
      const parsed = JSON.parse(raw) as ScenarioEvent[]
      return Array.isArray(parsed) ? parsed.slice(0, 12) : undefined
    } catch {
      return undefined
    }
  }, [params])

  const fail = useCallback(
    (cause: unknown) =>
      setError(
        cause instanceof ApiClientError ? (ar ? cause.failure.messageAr : cause.failure.message) : String(cause),
      ),
    [ar],
  )

  // ── Timeline: computed once per twin, then scrubbed locally ──────────────
  useEffect(() => {
    let cancelled = false
    ;(async () => {
      try {
        const data = await api.post<{
          frames: Array<TimelineFrame & { assets: SceneAssetState[] }>
        }>(`/api/digital-twin/${encodeURIComponent(state.twin.code)}/time-machine`, {
          offsets: [0, 15, 30, 60, 180, 360, 720, 1440],
        })
        if (cancelled) return
        setFrames(data.frames.map(({ offsetMin, peakRisk, stabilityIndex, breached }) => ({ offsetMin, peakRisk, stabilityIndex, breached })))
        setFutureAssets(Object.fromEntries(data.frames.map((frame) => [frame.offsetMin, frame.assets])))
        setFrameIndex(0)
      } catch {
        // A twin without a timeline is still a usable twin; the scene stands alone.
      }
    })()
    return () => {
      cancelled = true
    }
  }, [state.twin.code])

  const switchTwin = async (code: string) => {
    setError(null)
    setRunning(true)
    try {
      const data = await api.get<TwinStatePayload>(`/api/digital-twin/${encodeURIComponent(code)}/state`)
      const topology = await api.get<{ scene: TwinStatePayload['scene'] }>(
        `/api/digital-twin/${encodeURIComponent(code)}/topology`,
      )
      setState({ ...data, scene: topology.scene })
      setSelected(data.twin.assetCode ?? data.assets[0]?.code ?? null)
      setSimulation(null)
      setCascade([])
      setGhost(null)
      setSplit(null)
    } catch (cause) {
      fail(cause)
    } finally {
      setRunning(false)
    }
  }

  const runScenario = async (events: ScenarioEvent[]) => {
    if (events.length === 0) return
    setRunning(true)
    setError(null)
    try {
      const result = await api.post<{
        code: string
        riskBefore: number
        riskAfter: number
        summary: string
        summaryAr: string
        snapshotCode: string | null
        frames: TimelineFrame[]
      }>(`/api/digital-twin/${encodeURIComponent(state.twin.code)}/simulate`, { events })
      setSimulation(result)

      // Refresh the scene under the scenario so the picture matches the numbers.
      const stressed = await api.post<{ frames: Array<TimelineFrame & { assets: SceneAssetState[] }> }>(
        `/api/digital-twin/${encodeURIComponent(state.twin.code)}/time-machine`,
        { events, offsets: [0, 15, 30, 60, 180] },
      )
      setFrames(stressed.frames.map(({ offsetMin, peakRisk, stabilityIndex, breached }) => ({ offsetMin, peakRisk, stabilityIndex, breached })))
      setFutureAssets(Object.fromEntries(stressed.frames.map((frame) => [frame.offsetMin, frame.assets])))
      setFrameIndex(0)
    } catch (cause) {
      fail(cause)
    } finally {
      setRunning(false)
    }
  }

  const runCascade = async () => {
    if (!selected) return
    setRunning(true)
    setError(null)
    try {
      const result = await api.post<{ steps: CascadeStep[] }>(
        `/api/digital-twin/${encodeURIComponent(state.twin.code)}/cascade`,
        { assetCode: selected },
      )
      setCascade(result.steps)
      setLayers((current) => ({ ...current, cascade: true }))
    } catch (cause) {
      fail(cause)
    } finally {
      setRunning(false)
    }
  }

  const runGhost = async () => {
    if (!selected) return
    setRunning(true)
    setError(null)
    try {
      // The ghost is the same forward walk the timeline uses, read at one offset.
      const data = await api.post<{ frames: Array<TimelineFrame & { assets: SceneAssetState[] }> }>(
        `/api/digital-twin/${encodeURIComponent(state.twin.code)}/time-machine`,
        { offsets: [0, 30] },
      )
      const now = data.frames[0]?.assets.find((asset) => asset.code === selected)
      const later = data.frames[1]?.assets.find((asset) => asset.code === selected)
      const current = state.assets.find((asset) => asset.code === selected)
      if (now && later && current) {
        setGhost({
          present: { ...now, healthScore: current.healthScore },
          future: { ...later, healthScore: current.healthScore },
          offsetMin: 30,
          etaMinutes: null,
        })
      }
    } catch (cause) {
      fail(cause)
    } finally {
      setRunning(false)
    }
  }

  const runSplit = async () => {
    if (!selected) return
    setRunning(true)
    setError(null)
    try {
      const branches = await api.get<{
        branches: Array<{ key: string; risk: number; loadPct: number; tempC: number; breaches: boolean }>
      }>(`/api/future-branches?assetCode=${encodeURIComponent(selected)}`)

      // Two futures on one clock: the untouched trajectory and the same trajectory with
      // the recommended strategy in force. The offsets are the ones already computed.
      const noAction = branches.branches.find((branch) => branch.key === 'no_action')
      const strategy = branches.branches.find((branch) => branch.key === 'nabdh_strategy')
      if (!noAction || !strategy) return

      const offsets = frames.length > 0 ? frames.map((frame) => frame.offsetMin) : [0, 15, 30, 60]
      const shape = (target: typeof noAction, index: number, total: number) => {
        const progress = total <= 1 ? 1 : index / (total - 1)
        return {
          peakRisk: state.snapshot.peakRisk + (target.risk - state.snapshot.peakRisk) * progress,
          loadPct: target.loadPct,
          tempC: target.tempC,
        }
      }

      setSplit({
        without: offsets.map((offsetMin, index) => ({
          offsetMin,
          ...shape(noAction, index, offsets.length),
          breached: noAction.breaches && index >= offsets.length - 2,
        })),
        withNabdh: offsets.map((offsetMin, index) => ({
          offsetMin,
          ...shape(strategy, index, offsets.length),
          breached: strategy.breaches && index >= offsets.length - 1,
        })),
      })
    } catch (cause) {
      fail(cause)
    } finally {
      setRunning(false)
    }
  }

  // The scene shows the frame the timeline is parked on, which is what makes moving the
  // timeline change the picture rather than just a number.
  const displayedAssets = useMemo(() => {
    const frame = frames[frameIndex]
    if (!frame) return state.assets
    const future = futureAssets[frame.offsetMin]
    if (!future) return state.assets
    const byCode = new Map(future.map((asset) => [asset.code, asset]))
    return state.assets.map((asset) => {
      const forward = byCode.get(asset.code)
      return forward ? { ...asset, ...forward } : asset
    })
  }, [frames, frameIndex, futureAssets, state.assets])

  const selectedAsset = displayedAssets.find((asset) => asset.code === selected) ?? null

  return (
    <div className="min-w-0 space-y-4">
      {error ? <Notice tone="danger">{error}</Notice> : null}

      {/* ── Level and twin selection (§14) ──────────────────────────────── */}
      <div className="flex flex-wrap items-center gap-3">
        <Segmented
          ariaLabel={t('twin.levelHint')}
          size="sm"
          value={state.twin.level}
          onChange={(level) => {
            const target = twins.find((twin) => twin.level === level)
            if (target) void switchTwin(target.code)
          }}
          options={['national', 'region', 'site', 'asset']
            .filter((level) => twins.some((twin) => twin.level === level))
            .map((level) => ({ value: level, label: t(`twin.level.${level}`) }))}
        />

        {twins.filter((twin) => twin.level === state.twin.level).length > 1 ? (
          <select
            value={state.twin.code}
            onChange={(event) => void switchTwin(event.target.value)}
            aria-label={t('twin.title')}
            className="h-8 rounded-lg border border-border bg-surface-2 px-2 text-xs text-text"
          >
            {twins
              .filter((twin) => twin.level === state.twin.level)
              .map((twin) => (
                <option key={twin.code} value={twin.code}>
                  {ar ? twin.nameAr : twin.name}
                </option>
              ))}
          </select>
        ) : null}

        <span className="text-[11px] text-text-faint">{t('twin.levelHint')}</span>

        <Badge tone="muted" className="ms-auto">
          {t(`twin.autonomyLevel.${state.twin.autonomyLevel}`)}
        </Badge>
      </div>

      <div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
        <StatCard
          label={t('commandCenter.cards.gridStability')}
          value={n(state.snapshot.stabilityIndex, { maximumFractionDigits: 1 })}
        />
        <StatCard
          label={t('common.riskScore')}
          value={n(state.snapshot.peakRisk, { maximumFractionDigits: 0 })}
          unit="%"
          state={state.snapshot.peakRisk >= 70 ? 'critical' : state.snapshot.peakRisk >= 45 ? 'warning' : 'normal'}
        />
        <StatCard label={t('twin.members')} value={n(state.assets.length)} />
        <StatCard
          label={t('twin.fidelity')}
          value={n(state.fidelity.score, { maximumFractionDigits: 0 })}
          unit="%"
          hint={t(`twin.fidelityBand.${state.fidelity.band}`)}
          state={state.fidelity.score >= 75 ? 'normal' : 'warning'}
        />
      </div>

      <div className="grid gap-4 xl:grid-cols-[1fr_23rem]">
        <div className="min-w-0 space-y-4">
          <Panel>
            <PanelHeader
              title={ar ? state.twin.nameAr : state.twin.name}
              subtitle={view === 'xray' ? t('twin.view.xrayHint') : view === 'thermal' ? t('twin.view.thermalHint') : t('twin.subtitle')}
              action={
                <Segmented
                  ariaLabel={t('twin.view.standard')}
                  size="sm"
                  value={view}
                  onChange={setView}
                  options={VIEWS.map((entry) => ({ value: entry, label: t(`twin.view.${entry}`) }))}
                />
              }
            />
            <PanelBody className="pt-0">
              <TwinCanvas
                nodes={state.scene.nodes}
                edges={state.scene.edges}
                assets={displayedAssets}
                radius={state.scene.radius}
                view={view}
                layers={layers}
                selectedCode={selected}
                onSelect={setSelected}
                propagation={cascade}
              />

              {/* ── Layers (§10) ────────────────────────────────────────── */}
              <div className="mt-3 flex flex-wrap gap-x-4 gap-y-2 border-t border-border/70 pt-3">
                <span className="text-[10px] uppercase tracking-wide text-text-faint">{t('twin.layers')}</span>
                {LAYER_KEYS.map((key) => (
                  <label key={key} className="flex items-center gap-1.5 text-[11px] text-text-muted">
                    <input
                      type="checkbox"
                      checked={layers[key]}
                      onChange={(event) => setLayers((current) => ({ ...current, [key]: event.target.checked }))}
                      className="size-3 accent-brand"
                    />
                    {t(`twin.layer.${key === 'labels' ? 'assets' : key}`)}
                  </label>
                ))}
              </div>
            </PanelBody>
          </Panel>

          {frames.length > 0 ? (
            <TwinTimeline frames={frames} index={frameIndex} onIndexChange={setFrameIndex} />
          ) : null}

          {canSimulate ? (
            <div className="flex flex-wrap gap-2">
              <Button size="sm" variant="secondary" onClick={runCascade} disabled={!selected} loading={running}>
                {t('cascade.run')}
              </Button>
              <Button size="sm" variant="secondary" onClick={runGhost} disabled={!selected}>
                {t('twin.ghost')}
              </Button>
              <Button size="sm" variant="secondary" onClick={runSplit} disabled={!selected}>
                {t('twin.split')}
              </Button>
              <Button
                size="sm"
                variant="ghost"
                onClick={() => router.push(`/war-room?asset=${encodeURIComponent(selected ?? '')}`)}
                disabled={!selected}
              >
                {t('warRoom.title')}
              </Button>
            </div>
          ) : null}

          <SimulationProgress running={running} />

          {simulation ? (
            <Panel>
              <PanelHeader
                title={t('reports.beforeAfter')}
                subtitle={ar ? simulation.summaryAr : simulation.summary}
                action={<Badge tone="accent">{simulation.code}</Badge>}
              />
              <PanelBody className="pt-0">
                <div className="flex flex-wrap items-baseline gap-6">
                  <div>
                    <p className="text-[10px] uppercase tracking-wide text-text-faint">{t('counterfactual.before')}</p>
                    <p className="tnum text-2xl font-semibold text-critical">
                      {n(simulation.riskBefore, { maximumFractionDigits: 0 })}%
                    </p>
                  </div>
                  <span aria-hidden className="text-text-faint">→</span>
                  <div>
                    <p className="text-[10px] uppercase tracking-wide text-text-faint">{t('counterfactual.after')}</p>
                    <p className="tnum text-2xl font-semibold text-normal">
                      {n(simulation.riskAfter, { maximumFractionDigits: 0 })}%
                    </p>
                  </div>
                  {simulation.snapshotCode ? (
                    <span className="ms-auto text-[10px] text-text-faint">
                      {t('twin.snapshotTaken')} · {simulation.snapshotCode}
                    </span>
                  ) : null}
                </div>
              </PanelBody>
            </Panel>
          ) : null}

          {ghost ? (
            <GhostFuture
              present={ghost.present}
              future={ghost.future}
              offsetMin={ghost.offsetMin}
              etaMinutes={ghost.etaMinutes}
            />
          ) : null}

          {split ? <SplitFuture without={split.without} withNabdh={split.withNabdh} /> : null}

          {canSimulate ? (
            <ScenarioCanvas
              twinCode={state.twin.code}
              presets={presets}
              regions={regions}
              initialEvents={handoverEvents}
              onRun={runScenario}
              running={running}
            />
          ) : null}
        </div>

        <div className="min-w-0 space-y-4">
          {selectedAsset ? (
            <TwinInspector
              twinCode={state.twin.code}
              state={selectedAsset}
              simulations={simulations}
              snapshots={snapshots}
            />
          ) : (
            <Panel>
              <PanelHeader title={t('twin.inspector')} />
              <PanelBody>
                <p className="text-xs text-text-muted">{t('twin.selectAsset')}</p>
              </PanelBody>
            </Panel>
          )}

          <FidelityPanel fidelity={state.fidelity} deviations={deviations} />

          <Panel>
            <PanelHeader title={t('twin.autonomy')} subtitle={t('twin.autonomyNote')} />
            <PanelBody className="pt-0">
              <ol className="space-y-1.5">
                {[0, 1, 2, 3, 4].map((level) => (
                  <li key={level} className="flex items-center gap-2 text-[11px]">
                    <span
                      aria-hidden
                      className={cn(
                        'size-1.5 rounded-full',
                        level < state.twin.autonomyLevel
                          ? 'bg-normal'
                          : level === state.twin.autonomyLevel
                            ? 'bg-brand nabdh-pulse'
                            : 'bg-border-strong',
                      )}
                    />
                    <span className={cn(level > state.twin.autonomyLevel ? 'text-text-faint line-through' : 'text-text-muted')}>
                      {t(`twin.autonomyLevel.${level}`)}
                    </span>
                  </li>
                ))}
              </ol>
            </PanelBody>
          </Panel>

          <Panel>
            <PanelHeader
              title={canSimulate ? t('twin.mode.sandbox') : t('twin.mode.shadow')}
              subtitle={canSimulate ? t('twin.mode.sandboxBody') : t('twin.mode.shadowBody')}
              action={<Badge tone={canSimulate ? 'accent' : 'muted'}>{t(`mode.${canSimulate ? 'sandbox' : 'live'}.short`)}</Badge>}
            />
          </Panel>
        </div>
      </div>
    </div>
  )
}
