'use client'

import { useCallback, useState } from 'react'

import { Button, Notice, Segmented, Select } from '@/components/ui/controls'
import {
  Badge,
  EmptyState,
  Meter,
  Panel,
  PanelBody,
  PanelHeader,
  StatCard,
} from '@/components/ui/display'
import { useI18n } from '@/components/providers/i18n-provider'
import { api } from '@/lib/api/client'
import { cn } from '@/lib/cn'

/**
 * The Grid Physics console (5.0).
 *
 * One screen for the questions a solved load flow can answer and a demand allocation
 * cannot: what is the voltage and angle at every bus, which paths are past their rating,
 * how close is the weakest bus to collapse, and does the grid still stand if any single
 * thing fails.
 *
 * The discipline that shapes every panel here: **a result carries the method that produced
 * it, and a case that did not converge is never rendered as an answer.** The solver
 * reports its own status, and when that status is anything but `converged` this screen
 * shows the status and withholds the numbers rather than drawing a picture from the last
 * iterate.
 */

interface FlowSolution {
  mode: 'fast' | 'ac'
  twinCode: string
  ts: number
  durationMs: number
  usable: boolean
  offsetMin: number
  stateKind: string
  forecastConfidence: number
  note: string
  impedanceSource: string | null
  slackReason: string | null
  islanded: string[]
  excluded: Array<{ code: string; reason: string }>
  unsuppliedBuses: number
  islandsSolved: Array<{
    slackCode: string | null
    buses: number
    energised: boolean
    converged: boolean
    solverStatus: string
    generationMw: number
    demandMw: number
    lossMw: number
    dispatchScale: number
  }>
  ac: {
    method: string
    solverStatus: string
    converged: boolean
    iterations: number
    convergence: number
    tolerance: number
    totalGenerationMw: number
    totalLoadMw: number
    totalLossMw: number
    totalLossMvar: number
    buses: Array<{ code: string; type: string; finalType: string; vPu: number; vKv: number; angleDeg: number; pMw: number; qMvar: number; vDeviationPct: number; qLimited: boolean }>
    branches: Array<{ from: string; to: string; kind: string; pFromMw: number; qFromMvar: number; currentKa: number; loadingPct: number; lossMw: number; direction: string; tap: number }>
    voltageViolations: Array<{ code: string; vPu: number; kind: string }>
    overloads: Array<{ from: string; to: string; loadingPct: number }>
  } | null
  fast: { method: string; totalLossMw: number; bottlenecks: string[]; edges: unknown[] } | null
}

interface HealthReport {
  readyToSolve: boolean
  validation: {
    errors: number
    warnings: number
    solvable: boolean
    assetsChecked: number
    branchesChecked: number
    findings: Array<{ code: string; severity: string; subject: string; message: string; messageAr: string }>
  }
  calibration: {
    calibrationRisk: number
    overCount: number
    impossibleCount: number
    totalShortfallMw: number
    note: string
    worst: { code: string; utilisationPct: number } | null
    entries: Array<{ code: string; assetTypeKey: string; downstreamDemandMw: number; ratedCapacityMw: number; utilisationPct: number; band: string; shortfallMw: number }>
  }
}

interface ContingencyReport {
  depth: number
  baseConverged: boolean
  scanned: number
  candidates: number
  budgetExhausted: boolean
  nMinusOneSecure: boolean
  secureCount: number
  note: string
  durationMs: number
  scope: { slackCode: string | null; buses: number; coveragePct: number; reason: string }
  violations: Array<{
    label: string
    outcome: string
    severity: number
    converged: boolean
    solverStatus: string
    worstLoadingPct: number
    worstLoadingBranch: string | null
    worstVoltagePu: number
    unservedMw: number
    islandedAssets: string[]
  }>
}

interface Stability {
  converged: boolean
  solverStatus: string
  stabilityIndex: number
  collapseRisk: string
  coveragePct: number
  busesAssessed: number
  busesUnsolved: number
  criticalReactiveMvar: number
  note: string
  weakBuses: Array<{ code: string; vPu: number; angleDeg: number; marginPct: number; reactiveDemandMvar: number; band: string }>
}

interface Balance {
  method: string
  generationMw: number
  storageDischargeMw: number
  storageChargeMw: number
  loadMw: number
  lossMw: number
  balanceErrorMw: number
  balanceErrorPct: number
  dataQualityPct: number
  converged: boolean
  note: string
}

interface StateEstimate {
  measuredCount: number
  estimatedCount: number
  observabilityPct: number
  note: string
  quantities: Array<{ assetCode: string; quantity: string; value: number; unit: string; origin: string; sensorCode: string | null; confidence: number }>
}

interface Thermal {
  withoutData: number
  note: string
  worst: { code: string; tempC: number | null; headroomC: number | null; timeToLimitMin: number | null } | null
  entries: Array<{ code: string; assetTypeKey: string; tempC: number | null; ratedTempC: number | null; headroomC: number | null; currentLoadingPct: number; thermalLoadingPct: number | null; timeToLimitMin: number | null; unavailableReason: string | null }>
}

interface BatteryOption {
  code: string
  name: string
  nameAr: string
  ratedPowerMw: number
  socPct: number
  socOrigin: string
  state: string
  isOutage: boolean
}

interface Dispatch {
  batteryCode: string
  batteryName: string
  batteryNameAr: string
  simulatedOnly: boolean
  requestMw: number
  durationMin: number
  deliveredMwAvg: number
  shortfallMwAvg: number
  sustainedMin: number
  maxSustainMin: number
  meetsRequest: boolean
  socStartPct: number
  socEndPct: number
  socOrigin: string
  energyDeliveredMwh: number
  energyDrawnMwh: number
  equivalentCycles: number
  usableEnergyMwh: number
  ratedPowerMw: number
  steps: Array<{ minute: number; powerMw: number; socPct: number; limited: boolean; limitedBy: string }>
  assumptions: Array<{ key: string; text: string; textAr: string }>
  note: string
  noteAr: string
}

type TabKey = 'flow' | 'model' | 'contingency' | 'stability' | 'thermal' | 'balance' | 'state' | 'storage'

const TABS: TabKey[] = ['flow', 'model', 'contingency', 'stability', 'thermal', 'balance', 'state', 'storage']

function bandTone(band: string): string {
  switch (band) {
    case 'impossible':
    case 'critical':
      return 'text-critical'
    case 'over':
    case 'weak':
      return 'text-warning'
    case 'tight':
    case 'watch':
      return 'text-watch'
    default:
      return 'text-normal'
  }
}

export function GridPhysicsConsole({
  twins,
  initialTwin,
  canSimulate,
}: {
  twins: Array<{ code: string; name: string; nameAr: string; level: string }>
  initialTwin: string
  canSimulate: boolean
}) {
  const { t, locale, n, mw, pct } = useI18n()
  const ar = locale === 'ar'

  const [twin, setTwin] = useState(initialTwin)
  const [mode, setMode] = useState<'fast' | 'ac'>('ac')
  /** Minutes ahead to solve. Zero is the grid as it stands; anything else is a forecast. */
  const [offsetMin, setOffsetMin] = useState(0)
  const [tab, setTab] = useState<TabKey>('flow')

  const [flow, setFlow] = useState<FlowSolution | null>(null)
  const [health, setHealth] = useState<HealthReport | null>(null)
  const [contingency, setContingency] = useState<ContingencyReport | null>(null)
  const [stability, setStability] = useState<Stability | null>(null)
  const [balance, setBalance] = useState<Balance | null>(null)
  const [estimate, setEstimate] = useState<StateEstimate | null>(null)
  const [thermal, setThermal] = useState<Thermal | null>(null)
  const [batteries, setBatteries] = useState<BatteryOption[]>([])
  const [battery, setBattery] = useState<string>('')
  const [requestMw, setRequestMw] = useState<number>(30)
  const [durationMin, setDurationMin] = useState<number>(60)
  const [dispatch, setDispatch] = useState<Dispatch | null>(null)

  const [busy, setBusy] = useState<string | null>(null)
  const [error, setError] = useState<string | null>(null)

  const run = useCallback(async (label: string, task: () => Promise<void>) => {
    setBusy(label)
    setError(null)
    try {
      await task()
    } catch (caught) {
      setError(caught instanceof Error ? caught.message : 'Request failed')
    } finally {
      setBusy(null)
    }
  }, [])

  const solve = () =>
    run('solve', async () => {
      const [f, h] = await Promise.all([
        api.get<FlowSolution>(`/api/grid/flow?twin=${twin}&mode=${mode}&offset=${offsetMin}`),
        api.get<HealthReport>(`/api/grid/validate?twin=${twin}`),
      ])
      setFlow(f)
      setHealth(h)
    })

  const loadBatteries = () =>
    run('batteries', async () => {
      const payload = await api.get<{ batteries: BatteryOption[] }>(
        `/api/storage/dispatch?twin=${twin}`,
      )
      setBatteries(payload.batteries)
      setBattery((current) =>
        current && payload.batteries.some((entry) => entry.code === current)
          ? current
          : (payload.batteries[0]?.code ?? ''),
      )
    })

  const runDispatch = () =>
    run('dispatch', async () => {
      setDispatch(
        await api.post<Dispatch>('/api/storage/dispatch', {
          twinCode: twin,
          assetCode: battery,
          requestMw,
          durationMin,
        }),
      )
    })

  const runContingency = (depth: 1 | 2) =>
    run(`n${depth}`, async () => {
      setContingency(
        await api.post<ContingencyReport>('/api/grid/contingency', {
          twinCode: twin,
          depth,
          maxCases: depth === 1 ? 120 : 80,
        }),
      )
      setTab('contingency')
    })

  const loadStability = () =>
    run('stability', async () => {
      setStability(await api.get<Stability>(`/api/grid/stability?twin=${twin}`))
      setTab('stability')
    })

  const loadBalance = () =>
    run('balance', async () => {
      setBalance(await api.get<Balance>(`/api/grid/balance?twin=${twin}&mode=${mode}`))
      setTab('balance')
    })

  const loadEstimate = () =>
    run('estimate', async () => {
      setEstimate(await api.get<StateEstimate>(`/api/grid/state-estimate?twin=${twin}`))
      setTab('state')
    })

  const loadThermal = () =>
    run('thermal', async () => {
      setThermal(await api.get<Thermal>(`/api/grid/thermal?twin=${twin}`))
      setTab('thermal')
    })

  const ac = flow?.ac ?? null
  const converged = Boolean(ac?.converged)

  return (
    <div className="space-y-4" data-testid="grid-physics-console">
      <Notice tone="info">{t('gridPhysics.note')}</Notice>

      {/* ── Controls ─────────────────────────────────────────────────────── */}
      <Panel>
        <PanelBody className="flex flex-wrap items-end gap-3">
          <label className="block">
            <span className="mb-1 block text-[11px] text-text-muted">{t('liveGrid.view')}</span>
            <Select value={twin} onChange={(event) => setTwin(event.target.value)}>
              {twins.map((entry) => (
                <option key={entry.code} value={entry.code}>
                  {ar ? entry.nameAr : entry.name}
                </option>
              ))}
            </Select>
          </label>

          <label className="block">
            <span className="mb-1 block text-[11px] text-text-muted">{t('gridPhysics.mode')}</span>
            <Segmented
              value={mode}
              onChange={setMode}
              ariaLabel={t('gridPhysics.mode')}
              options={[
                { value: 'fast' as const, label: t('gridPhysics.modeFast'), title: t('gridPhysics.modeFastNote') },
                { value: 'ac' as const, label: t('gridPhysics.modeAc'), title: t('gridPhysics.modeAcNote') },
              ]}
            />
          </label>

          <label className="block">
            <span className="mb-1 block text-[11px] text-text-muted">{t('gridPhysics.horizon')}</span>
            <Select value={String(offsetMin)} onChange={(event) => setOffsetMin(Number(event.target.value))}>
              {[0, 30, 60, 180, 360].map((minutes) => (
                <option key={minutes} value={minutes}>
                  {minutes === 0 ? t('gridPhysics.horizonNow') : `+${minutes} min`}
                </option>
              ))}
            </Select>
          </label>

          <Button onClick={solve} disabled={busy !== null} data-testid="solve">
            {busy === 'solve' ? t('gridPhysics.solving') : t('gridPhysics.solve')}
          </Button>

          {canSimulate ? (
            <>
              <Button variant="secondary" onClick={() => runContingency(1)} disabled={busy !== null} data-testid="run-n1">
                {busy === 'n1' ? t('gridPhysics.running') : t('gridPhysics.runN1')}
              </Button>
              <Button variant="ghost" onClick={() => runContingency(2)} disabled={busy !== null} data-testid="run-n2">
                {busy === 'n2' ? t('gridPhysics.running') : t('gridPhysics.runN2')}
              </Button>
            </>
          ) : null}

          <Button variant="ghost" onClick={loadStability} disabled={busy !== null}>
            {t('gridPhysics.stability')}
          </Button>
          <Button variant="ghost" onClick={loadThermal} disabled={busy !== null}>
            {t('gridPhysics.thermal')}
          </Button>
          <Button variant="ghost" onClick={loadBalance} disabled={busy !== null}>
            {t('gridPhysics.balance')}
          </Button>
          <Button variant="ghost" onClick={loadEstimate} disabled={busy !== null}>
            {t('gridPhysics.stateEstimate')}
          </Button>
        </PanelBody>
      </Panel>

      {error ? <Notice tone="danger">{error}</Notice> : null}

      {/* ── Solver banner ────────────────────────────────────────────────── */}
      {flow ? (
        <div
          className={cn(
            'rounded-[--radius-panel] border px-3.5 py-2.5 text-[11px] leading-relaxed',
            flow.usable ? 'border-normal/30 bg-normal/8' : 'border-warning/40 bg-warning/8',
          )}
          data-testid="solver-banner"
          data-converged={String(flow.usable)}
        >
          <div className="flex flex-wrap items-center gap-x-5 gap-y-1">
            <span>
              <span className="text-text-faint">{t('gridPhysics.solverStatus')} </span>
              {flow.stateKind === 'predicted' ? (
                <span
                  className="me-2 rounded border border-info/40 bg-info/10 px-1.5 py-0.5 font-mono text-[10px] uppercase text-info"
                  data-testid="flow-predicted"
                >
                  {t('source.predicted')} · +{flow.offsetMin} min · {n(flow.forecastConfidence, { maximumFractionDigits: 1 })} %
                </span>
              ) : null}
              <span className={cn('font-mono font-medium', flow.usable ? 'text-normal' : 'text-warning')}>
                {ac ? ac.method : flow.fast?.method}
                {ac ? ` · ${ac.solverStatus}` : ''}
              </span>
            </span>
            {ac ? (
              <>
                <span>
                  <span className="text-text-faint">{t('gridPhysics.iterations')} </span>
                  <span className="font-mono tabular-nums">{n(ac.iterations)}</span>
                </span>
                <span>
                  <span className="text-text-faint">{t('gridPhysics.convergence')} </span>
                  <span className="font-mono tabular-nums">{ac.convergence.toExponential(2)}</span>
                </span>
                <span>
                  <span className="text-text-faint">{t('gridPhysics.tolerance')} </span>
                  <span className="font-mono tabular-nums">{ac.tolerance.toExponential(0)}</span>
                </span>
              </>
            ) : null}
            <span>
              <span className="text-text-faint">ms </span>
              <span className="font-mono tabular-nums">{n(flow.durationMs)}</span>
            </span>
            {flow.impedanceSource ? (
              <Badge tone="accent">{t(`proof.impedance.${flow.impedanceSource}`)}</Badge>
            ) : null}
          </div>
          <p className="mt-1 text-text-muted">{flow.note}</p>
        </div>
      ) : null}

      {!flow ? <EmptyState title={t('uiState.empty')} body={t('gridPhysics.empty')} /> : null}

      {flow ? (
        <>
          <Segmented
            value={tab}
            onChange={setTab}
            ariaLabel={t('gridPhysics.title')}
            options={TABS.map((key) => ({
              value: key,
              label:
                key === 'flow'
                  ? t('gridPhysics.modeAc')
                  : key === 'model'
                    ? t('gridPhysics.validate')
                    : key === 'contingency'
                      ? t('gridPhysics.contingency')
                      : key === 'stability'
                        ? t('gridPhysics.stability')
                        : key === 'thermal'
                          ? t('gridPhysics.thermal')
                          : key === 'balance'
                            ? t('gridPhysics.balance')
                            : key === 'state'
                              ? t('gridPhysics.stateEstimate')
                              : t('storage.title'),
            }))}
          />

          {tab === 'flow' ? <FlowTab /> : null}
          {tab === 'model' ? <ModelTab /> : null}
          {tab === 'contingency' ? <ContingencyTab /> : null}
          {tab === 'stability' ? <StabilityTab /> : null}
          {tab === 'thermal' ? <ThermalTab /> : null}
          {tab === 'balance' ? <BalanceTab /> : null}
          {tab === 'state' ? <StateTab /> : null}
          {tab === 'storage' ? <StorageTab /> : null}
        </>
      ) : null}
    </div>
  )

  // ── Tabs ────────────────────────────────────────────────────────────────

  function FlowTab() {
    if (!flow) return null

    if (!ac) {
      return (
        <Panel>
          <PanelHeader title={t('gridPhysics.modeFast')} subtitle={t('gridPhysics.modeFastNote')} />
          <PanelBody>
            <div className="grid gap-2 sm:grid-cols-3">
              <StatCard label={t('gridPhysics.losses')} value={`${n(flow.fast?.totalLossMw ?? 0, { maximumFractionDigits: 1 })} MW`} />
              <StatCard label={t('liveGrid.bottlenecks')} value={n(flow.fast?.bottlenecks.length ?? 0)} />
              <StatCard label={t('gridPhysics.branches')} value={n(flow.fast?.edges.length ?? 0)} />
            </div>
          </PanelBody>
        </Panel>
      )
    }

    return (
      <div className="space-y-3">
        {/* A case that did not converge shows its status and withholds its numbers. */}
        {!converged ? (
          <Notice tone="warning" data-testid="not-converged">
            {t('gridPhysics.notConverged')} — {ac.solverStatus}
          </Notice>
        ) : null}

        <div className="grid gap-2 sm:grid-cols-3 lg:grid-cols-6">
          <StatCard label={t('gridPhysics.buses')} value={n(ac.buses.length)} />
          <StatCard label={t('gridPhysics.branches')} value={n(ac.branches.length)} />
          <StatCard label={t('gridPhysics.generation')} value={mw(ac.totalGenerationMw)} />
          <StatCard label={t('gridPhysics.load')} value={mw(ac.totalLoadMw)} />
          <StatCard label={t('gridPhysics.losses')} value={`${n(ac.totalLossMw, { maximumFractionDigits: 1 })} MW`} />
          <StatCard
            label={t('gridPhysics.violations')}
            value={n(ac.voltageViolations.length)}
            state={ac.voltageViolations.length > 0 ? 'warning' : 'normal'}
          />
        </div>

        {/* Areas: a fragmented model is solved area by area, and says so. */}
        <Panel>
          <PanelHeader title={t('gridPhysics.areas')} subtitle={t('gridPhysics.areasNote')} />
          <PanelBody className="space-y-2">
            <p className="text-[11px] text-text-muted">
              {n(flow.islandsSolved.filter((i) => i.energised).length)} {t('gridPhysics.energised')} ·{' '}
              {n(flow.unsuppliedBuses)} {t('gridPhysics.unsupplied')}
              {flow.slackReason ? ` · ${flow.slackReason}` : ''}
            </p>
            <ul className="space-y-1">
              {flow.islandsSolved.slice(0, 12).map((island, index) => (
                <li
                  key={`${island.slackCode}-${index}`}
                  data-area={island.slackCode ?? 'unsupplied'}
                  className="flex flex-wrap items-baseline justify-between gap-2 rounded-md border border-border bg-surface-2/40 px-2.5 py-1.5 text-[11px]"
                >
                  <span className="font-mono">
                    {island.slackCode ?? '—'} · {n(island.buses)} {t('gridPhysics.buses')}
                  </span>
                  <span className="flex items-center gap-2">
                    {island.dispatchScale < 1 ? (
                      <Badge tone="accent">
                        ×{n(island.dispatchScale, { maximumFractionDigits: 2 })} {t('gridPhysics.dispatchScaled')}
                      </Badge>
                    ) : null}
                    <span className="font-mono tabular-nums text-text-muted">
                      {n(island.generationMw, { maximumFractionDigits: 0 })} / {n(island.demandMw, { maximumFractionDigits: 0 })} MW
                    </span>
                    <span className={island.converged ? 'text-normal' : island.energised ? 'text-warning' : 'text-text-faint'}>
                      {island.energised ? island.solverStatus : t('gridPhysics.unsupplied')}
                    </span>
                  </span>
                </li>
              ))}
            </ul>
          </PanelBody>
        </Panel>

        {converged ? (
          <div className="grid gap-3 xl:grid-cols-2">
            <Panel>
              <PanelHeader title={t('gridPhysics.voltageMagnitude')} subtitle={t('gridPhysics.voltageAngle')} />
              <PanelBody>
                <div className="max-h-80 overflow-auto">
                  <table className="w-full text-[11px]">
                    <thead className="sticky top-0 bg-surface-2 text-[10px] uppercase tracking-wide text-text-faint">
                      <tr>
                        <th className="py-1.5 text-start font-medium">{t('gridPhysics.buses')}</th>
                        <th className="py-1.5 text-end font-medium">pu</th>
                        <th className="py-1.5 text-end font-medium">kV</th>
                        <th className="py-1.5 text-end font-medium">θ°</th>
                        <th className="py-1.5 text-end font-medium">MW</th>
                        <th className="py-1.5 text-end font-medium">MVAr</th>
                      </tr>
                    </thead>
                    <tbody>
                      {[...ac.buses]
                        .sort((a, b) => a.vPu - b.vPu)
                        .slice(0, 40)
                        .map((bus) => (
                          <tr key={bus.code} className="border-b border-border/50" data-bus={bus.code}>
                            <td className="py-1 font-mono">
                              {bus.code}
                              {bus.type !== 'pq' ? (
                                <span className="ms-1.5 text-[9px] uppercase text-text-faint">{bus.finalType}</span>
                              ) : null}
                              {bus.qLimited ? <span className="ms-1 text-watch">Q*</span> : null}
                            </td>
                            <td className={cn('py-1 text-end font-mono tabular-nums', bus.vPu < 0.94 || bus.vPu > 1.06 ? 'text-critical' : '')}>
                              {n(bus.vPu, { maximumFractionDigits: 4 })}
                            </td>
                            <td className="py-1 text-end font-mono tabular-nums text-text-muted">{n(bus.vKv, { maximumFractionDigits: 1 })}</td>
                            <td className="py-1 text-end font-mono tabular-nums">{n(bus.angleDeg, { maximumFractionDigits: 2 })}</td>
                            <td className="py-1 text-end font-mono tabular-nums">{n(bus.pMw, { maximumFractionDigits: 1 })}</td>
                            <td className="py-1 text-end font-mono tabular-nums text-text-muted">{n(bus.qMvar, { maximumFractionDigits: 1 })}</td>
                          </tr>
                        ))}
                    </tbody>
                  </table>
                </div>
              </PanelBody>
            </Panel>

            <Panel>
              <PanelHeader title={t('gridPhysics.branches')} subtitle={t('gridPhysics.overloads')} />
              <PanelBody>
                <div className="max-h-80 overflow-auto">
                  <table className="w-full text-[11px]">
                    <thead className="sticky top-0 bg-surface-2 text-[10px] uppercase tracking-wide text-text-faint">
                      <tr>
                        <th className="py-1.5 text-start font-medium">{t('gridPhysics.branches')}</th>
                        <th className="py-1.5 text-end font-medium">MW</th>
                        <th className="py-1.5 text-end font-medium">MVAr</th>
                        <th className="py-1.5 text-end font-medium">kA</th>
                        <th className="py-1.5 text-end font-medium">%</th>
                        <th className="py-1.5 text-end font-medium">{t('gridPhysics.losses')}</th>
                      </tr>
                    </thead>
                    <tbody>
                      {[...ac.branches]
                        .sort((a, b) => b.loadingPct - a.loadingPct)
                        .slice(0, 40)
                        .map((branch) => (
                          <tr key={`${branch.from}-${branch.to}`} className="border-b border-border/50">
                            <td className="py-1 font-mono">
                              {branch.from} → {branch.to}
                              {branch.tap !== 1 ? <span className="ms-1 text-watch">tap {branch.tap}</span> : null}
                            </td>
                            <td className="py-1 text-end font-mono tabular-nums">{n(branch.pFromMw, { maximumFractionDigits: 1 })}</td>
                            <td className="py-1 text-end font-mono tabular-nums text-text-muted">{n(branch.qFromMvar, { maximumFractionDigits: 1 })}</td>
                            <td className="py-1 text-end font-mono tabular-nums">{n(branch.currentKa, { maximumFractionDigits: 3 })}</td>
                            <td className={cn('py-1 text-end font-mono tabular-nums', branch.loadingPct > 100 ? 'text-critical' : branch.loadingPct > 90 ? 'text-warning' : '')}>
                              {n(branch.loadingPct, { maximumFractionDigits: 1 })}
                            </td>
                            <td className="py-1 text-end font-mono tabular-nums text-text-muted">{n(branch.lossMw, { maximumFractionDigits: 3 })}</td>
                          </tr>
                        ))}
                    </tbody>
                  </table>
                </div>
              </PanelBody>
            </Panel>
          </div>
        ) : null}
      </div>
    )
  }

  function ModelTab() {
    if (!health) return <EmptyState title={t('uiState.empty')} body={t('gridPhysics.validateNote')} />
    return (
      <div className="space-y-3">
        <Panel>
          <PanelHeader
            title={t('gridPhysics.validate')}
            subtitle={t('gridPhysics.validateNote')}
            action={
              <Badge tone={health.validation.solvable ? 'brand' : 'accent'}>
                {health.validation.solvable ? t('gridPhysics.solvable') : t('gridPhysics.notSolvable')}
              </Badge>
            }
          />
          <PanelBody className="space-y-2">
            <div className="grid gap-2 sm:grid-cols-4">
              <StatCard label={t('gridPhysics.errors')} value={n(health.validation.errors)} state={health.validation.errors > 0 ? 'critical' : 'normal'} />
              <StatCard label={t('gridPhysics.warnings')} value={n(health.validation.warnings)} state={health.validation.warnings > 0 ? 'watch' : 'normal'} />
              <StatCard label={t('liveGrid.kpi.assetsAtRisk')} value={n(health.validation.assetsChecked)} />
              <StatCard label={t('gridPhysics.branches')} value={n(health.validation.branchesChecked)} />
            </div>
            <ul className="max-h-72 space-y-1 overflow-auto">
              {health.validation.findings.slice(0, 40).map((finding, index) => (
                <li
                  key={`${finding.code}-${finding.subject}-${index}`}
                  data-finding={finding.code}
                  className="flex flex-wrap items-baseline justify-between gap-2 rounded-md border border-border bg-surface-2/40 px-2.5 py-1.5 text-[11px]"
                >
                  <span>
                    <span className={cn('me-2 font-mono text-[10px] uppercase', finding.severity === 'error' ? 'text-critical' : 'text-watch')}>
                      {finding.severity}
                    </span>
                    <span className="me-2 font-mono text-text-faint">{finding.subject}</span>
                    {ar ? finding.messageAr : finding.message}
                  </span>
                </li>
              ))}
            </ul>
          </PanelBody>
        </Panel>

        <Panel>
          <PanelHeader title={t('gridPhysics.calibration')} subtitle={t('gridPhysics.calibrationNote')} />
          <PanelBody className="space-y-2">
            <div className="grid gap-2 sm:grid-cols-3">
              <StatCard
                label={t('gridPhysics.calibrationRisk')}
                value={pct(health.calibration.calibrationRisk, 1)}
                state={health.calibration.calibrationRisk > 50 ? 'critical' : health.calibration.calibrationRisk > 20 ? 'warning' : 'normal'}
              />
              <StatCard label={t('gridPhysics.overRated')} value={n(health.calibration.overCount)} />
              <StatCard label={t('gridPhysics.shortfall')} value={`${n(health.calibration.totalShortfallMw, { maximumFractionDigits: 0 })} MW`} />
            </div>
            <p className="text-[11px] text-text-muted">{health.calibration.note}</p>
            <ul className="max-h-64 space-y-1 overflow-auto">
              {health.calibration.entries.slice(0, 25).map((entry) => (
                <li
                  key={entry.code}
                  data-calibration={entry.code}
                  className="flex flex-wrap items-baseline justify-between gap-2 rounded-md border border-border bg-surface-2/40 px-2.5 py-1.5 text-[11px]"
                >
                  <span className="font-mono">{entry.code}</span>
                  <span className="flex items-center gap-3">
                    <span className="font-mono tabular-nums text-text-muted">
                      {n(entry.downstreamDemandMw, { maximumFractionDigits: 0 })} / {n(entry.ratedCapacityMw, { maximumFractionDigits: 0 })} MW
                    </span>
                    <span className={cn('font-mono tabular-nums', bandTone(entry.band))}>
                      {n(entry.utilisationPct, { maximumFractionDigits: 0 })}%
                    </span>
                    <span className={cn('text-[10px]', bandTone(entry.band))}>{t(`gridPhysics.band.${entry.band}`)}</span>
                  </span>
                </li>
              ))}
            </ul>
          </PanelBody>
        </Panel>
      </div>
    )
  }

  function ContingencyTab() {
    if (!contingency) return <EmptyState title={t('uiState.empty')} body={t('gridPhysics.contingencyNote')} />
    return (
      <Panel>
        <PanelHeader
          title={`N-${contingency.depth}`}
          subtitle={contingency.scope.reason}
          action={
            <Badge tone={contingency.nMinusOneSecure ? 'brand' : 'accent'}>
              {contingency.nMinusOneSecure ? t('gridPhysics.secure') : t('gridPhysics.insecure')}
            </Badge>
          }
        />
        <PanelBody className="space-y-2" data-testid="contingency-result">
          <div className="grid gap-2 sm:grid-cols-4">
            <StatCard label={t('gridPhysics.scope')} value={`${n(contingency.scope.buses)} · ${pct(contingency.scope.coveragePct, 0)}`} />
            <StatCard label={t('gridPhysics.scanned')} value={`${n(contingency.scanned)} / ${n(contingency.candidates)}`} />
            <StatCard label={t('gridPhysics.secure')} value={n(contingency.secureCount)} state="normal" />
            <StatCard label={t('gridPhysics.insecure')} value={n(contingency.violations.length)} state={contingency.violations.length > 0 ? 'warning' : 'normal'} />
          </div>
          <p className="text-[11px] text-text-muted">{contingency.note}</p>

          <ul className="max-h-96 space-y-1 overflow-auto">
            {contingency.violations.slice(0, 40).map((violation) => (
              <li
                key={violation.label}
                data-contingency={violation.label}
                className="rounded-md border border-border bg-surface-2/40 px-2.5 py-1.5 text-[11px]"
              >
                <div className="flex flex-wrap items-baseline justify-between gap-2">
                  <span className="font-mono">{violation.label}</span>
                  <span className="flex items-center gap-2">
                    <Badge tone="accent">{t(`gridPhysics.outcome.${violation.outcome}`)}</Badge>
                    <span className="font-mono tabular-nums text-warning">{n(violation.severity, { maximumFractionDigits: 0 })}</span>
                  </span>
                </div>
                <div className="mt-0.5 flex flex-wrap gap-3 text-[10px] text-text-faint">
                  {violation.converged ? (
                    <>
                      <span>
                        {t('gridPhysics.worstLoading')} {n(violation.worstLoadingPct, { maximumFractionDigits: 0 })}%
                        {violation.worstLoadingBranch ? ` · ${violation.worstLoadingBranch}` : ''}
                      </span>
                      <span>V {n(violation.worstVoltagePu, { maximumFractionDigits: 3 })} pu</span>
                    </>
                  ) : (
                    <span className="text-critical">{violation.solverStatus}</span>
                  )}
                  {violation.unservedMw > 0 ? (
                    <span className="text-critical">
                      {t('gridPhysics.unservedMw')} {n(violation.unservedMw, { maximumFractionDigits: 0 })} MW
                    </span>
                  ) : null}
                </div>
              </li>
            ))}
          </ul>
        </PanelBody>
      </Panel>
    )
  }

  function StabilityTab() {
    if (!stability) return <EmptyState title={t('uiState.empty')} body={t('gridPhysics.stabilityNote')} />
    return (
      <Panel>
        <PanelHeader title={t('gridPhysics.stability')} subtitle={t('gridPhysics.stabilityNote')} />
        <PanelBody className="space-y-2" data-testid="stability-result">
          <div className="grid gap-2 sm:grid-cols-4">
            <StatCard
              label={t('gridPhysics.stabilityIndex')}
              value={n(stability.stabilityIndex, { maximumFractionDigits: 1 })}
              state={stability.stabilityIndex < 20 ? 'critical' : stability.stabilityIndex < 45 ? 'warning' : 'normal'}
            />
            <StatCard label={t('gridPhysics.collapseRisk')} value={stability.collapseRisk} />
            <StatCard label={t('gridPhysics.coverage')} value={pct(stability.coveragePct, 1)} />
            <StatCard label="MVAr" value={n(stability.criticalReactiveMvar, { maximumFractionDigits: 0 })} />
          </div>
          <p className="text-[11px] text-text-muted">{stability.note}</p>
          <ul className="space-y-1">
            {stability.weakBuses.slice(0, 15).map((bus) => (
              <li key={bus.code} className="flex flex-wrap items-baseline justify-between gap-2 rounded-md border border-border bg-surface-2/40 px-2.5 py-1.5 text-[11px]">
                <span className="font-mono">{bus.code}</span>
                <span className="flex items-center gap-3 font-mono tabular-nums">
                  <span className={bandTone(bus.band)}>{n(bus.vPu, { maximumFractionDigits: 4 })} pu</span>
                  <span className="text-text-muted">θ {n(bus.angleDeg, { maximumFractionDigits: 2 })}°</span>
                  <span className="text-text-faint">{t('gridPhysics.margin')} {n(bus.marginPct, { maximumFractionDigits: 2 })}</span>
                </span>
              </li>
            ))}
          </ul>
        </PanelBody>
      </Panel>
    )
  }

  function ThermalTab() {
    if (!thermal) return <EmptyState title={t('uiState.empty')} body={t('gridPhysics.thermalNote')} />
    const withData = thermal.entries.filter((entry) => entry.tempC !== null)
    return (
      <Panel>
        <PanelHeader title={t('gridPhysics.thermal')} subtitle={t('gridPhysics.thermalNote')} />
        <PanelBody className="space-y-2">
          <p className="text-[11px] text-text-muted">
            {n(withData.length)} / {n(thermal.entries.length)} · {n(thermal.withoutData)} {t('gridPhysics.noThermalData')}
          </p>
          <ul className="max-h-96 space-y-1 overflow-auto">
            {withData
              .sort((a, b) => (a.headroomC ?? 0) - (b.headroomC ?? 0))
              .slice(0, 30)
              .map((entry) => (
                <li key={entry.code} className="flex flex-wrap items-baseline justify-between gap-2 rounded-md border border-border bg-surface-2/40 px-2.5 py-1.5 text-[11px]">
                  <span className="font-mono">{entry.code}</span>
                  <span className="flex items-center gap-3 font-mono tabular-nums">
                    <span>{n(entry.tempC ?? 0, { maximumFractionDigits: 1 })} °C</span>
                    <span className={cn((entry.headroomC ?? 0) < 5 ? 'text-critical' : (entry.headroomC ?? 0) < 15 ? 'text-warning' : 'text-text-muted')}>
                      {t('gridPhysics.headroom')} {n(entry.headroomC ?? 0, { maximumFractionDigits: 1 })}
                    </span>
                    <span className="text-text-faint">
                      {entry.timeToLimitMin === null ? '—' : `${n(entry.timeToLimitMin, { maximumFractionDigits: 0 })} min`}
                    </span>
                  </span>
                </li>
              ))}
          </ul>
        </PanelBody>
      </Panel>
    )
  }

  function BalanceTab() {
    if (!balance) return <EmptyState title={t('uiState.empty')} body={t('gridPhysics.balanceNote')} />
    return (
      <Panel>
        <PanelHeader title={t('gridPhysics.balance')} subtitle={t('gridPhysics.balanceNote')} />
        <PanelBody className="space-y-2" data-testid="balance-result">
          <div className="grid gap-2 sm:grid-cols-3 lg:grid-cols-6">
            <StatCard label={t('gridPhysics.generation')} value={mw(balance.generationMw)} />
            <StatCard label={t('liveGrid.soc')} value={mw(balance.storageDischargeMw)} />
            <StatCard label={t('gridPhysics.load')} value={mw(balance.loadMw)} />
            <StatCard label={t('gridPhysics.losses')} value={`${n(balance.lossMw, { maximumFractionDigits: 1 })} MW`} />
            <StatCard
              label={t('gridPhysics.balanceError')}
              value={`${n(balance.balanceErrorMw, { maximumFractionDigits: 2 })} MW`}
              state={Math.abs(balance.balanceErrorPct) > 2 ? 'warning' : 'normal'}
            />
            <StatCard label={t('liveGrid.kpi.dataConfidence')} value={pct(balance.dataQualityPct, 1)} />
          </div>
          <p className="text-[11px] text-text-muted">{balance.note}</p>
        </PanelBody>
      </Panel>
    )
  }

  /**
   * Battery dispatch (§19).
   *
   * A projection of what a unit could deliver and for how long — never an instruction. The
   * state of charge it starts from is an estimate, and says so: the twin has no SOC meter,
   * and presenting a derived figure as a measurement is what §21 forbids.
   */
  function StorageTab() {
    return (
      <Panel>
        <PanelHeader title={t('storage.title')} subtitle={t('storage.note')} />
        <PanelBody className="space-y-3" data-testid="storage-dispatch">
          <div className="flex flex-wrap items-end gap-3">
            <Button variant="ghost" onClick={loadBatteries} disabled={busy !== null}>
              {t('storage.load')}
            </Button>

            {batteries.length > 0 ? (
              <>
                <label className="block">
                  <span className="mb-1 block text-[11px] text-text-muted">{t('storage.unit')}</span>
                  <Select value={battery} onChange={(event) => setBattery(event.target.value)}>
                    {batteries.map((entry) => (
                      <option key={entry.code} value={entry.code}>
                        {entry.code} — {n(entry.ratedPowerMw, { maximumFractionDigits: 0 })} MW ·{' '}
                        {n(entry.socPct, { maximumFractionDigits: 0 })} %
                      </option>
                    ))}
                  </Select>
                </label>

                <label className="block">
                  <span className="mb-1 block text-[11px] text-text-muted">{t('storage.request')}</span>
                  <Select
                    value={String(requestMw)}
                    onChange={(event) => setRequestMw(Number(event.target.value))}
                  >
                    {[-50, -20, 10, 20, 30, 45, 60, 100].map((value) => (
                      <option key={value} value={value}>
                        {value > 0 ? `+${value}` : value} MW
                      </option>
                    ))}
                  </Select>
                </label>

                <label className="block">
                  <span className="mb-1 block text-[11px] text-text-muted">{t('storage.duration')}</span>
                  <Select
                    value={String(durationMin)}
                    onChange={(event) => setDurationMin(Number(event.target.value))}
                  >
                    {[15, 30, 60, 120, 240].map((value) => (
                      <option key={value} value={value}>
                        {value} min
                      </option>
                    ))}
                  </Select>
                </label>

                <Button onClick={runDispatch} disabled={busy !== null || !battery} data-testid="run-dispatch">
                  {busy === 'dispatch' ? t('gridPhysics.running') : t('storage.simulate')}
                </Button>
              </>
            ) : null}
          </div>

          <Notice tone="info">{t('storage.noCommand')}</Notice>

          {!dispatch ? (
            <EmptyState title={t('uiState.empty')} body={t('storage.empty')} />
          ) : (
            <div className="space-y-3" data-testid="dispatch-result" data-meets={dispatch.meetsRequest}>
              <div className="grid gap-2 sm:grid-cols-4">
                <StatCard
                  label={t('storage.delivered')}
                  value={`${n(dispatch.deliveredMwAvg, { maximumFractionDigits: 1 })} MW`}
                  state={dispatch.meetsRequest ? 'normal' : 'warning'}
                />
                <StatCard label={t('storage.sustained')} value={`${n(dispatch.sustainedMin)} min`} />
                <StatCard
                  label={t('storage.soc')}
                  value={`${n(dispatch.socStartPct, { maximumFractionDigits: 0 })} → ${n(dispatch.socEndPct, { maximumFractionDigits: 0 })} %`}
                  hint={t('gridPhysics.estimated')}
                />
                <StatCard
                  label={t('storage.cycles')}
                  value={n(dispatch.equivalentCycles, { maximumFractionDigits: 3 })}
                />
              </div>

              <p className="text-[11px] leading-relaxed text-text-muted">
                {ar ? dispatch.noteAr : dispatch.note}
              </p>

              {/* The trajectory. The moment a limit binds is the point of the whole panel. */}
              <ul className="max-h-72 space-y-1 overflow-auto">
                {dispatch.steps
                  .filter((step, index) => index % Math.ceil(dispatch.steps.length / 40 || 1) === 0)
                  .map((step) => (
                    <li
                      key={step.minute}
                      data-limited={step.limited}
                      className={cn(
                        'flex items-baseline justify-between gap-2 rounded-md border px-2.5 py-1 text-[11px]',
                        step.limited ? 'border-warning/40 bg-warning/8' : 'border-border bg-surface-2/40',
                      )}
                    >
                      <span className="font-mono tabular-nums">{step.minute} min</span>
                      <span className="font-mono tabular-nums">
                        {n(step.powerMw, { maximumFractionDigits: 1 })} MW
                      </span>
                      <span className="font-mono tabular-nums">
                        {n(step.socPct, { maximumFractionDigits: 1 })} %
                      </span>
                      <span className="font-mono text-[10px] uppercase text-text-faint">
                        {step.limitedBy === 'none' ? '—' : t(`storage.limit.${step.limitedBy}`)}
                      </span>
                    </li>
                  ))}
              </ul>

              <div className="rounded-md border border-border bg-surface-2/40 p-2.5 text-[11px]">
                <div className="font-medium">{t('storage.assumptions')}</div>
                <ul className="space-y-0.5 pt-1 text-text-muted">
                  {dispatch.assumptions.map((entry) => (
                    <li key={entry.key}>{ar ? entry.textAr : entry.text}</li>
                  ))}
                </ul>
              </div>
            </div>
          )}
        </PanelBody>
      </Panel>
    )
  }

  function StateTab() {
    if (!estimate) return <EmptyState title={t('uiState.empty')} body={t('gridPhysics.stateNote')} />
    return (
      <Panel>
        <PanelHeader title={t('gridPhysics.stateEstimate')} subtitle={t('gridPhysics.stateNote')} />
        <PanelBody className="space-y-2" data-testid="state-estimate">
          <div className="grid gap-2 sm:grid-cols-3">
            <StatCard label={t('gridPhysics.measured')} value={n(estimate.measuredCount)} state="normal" />
            <StatCard label={t('gridPhysics.estimated')} value={n(estimate.estimatedCount)} />
            <StatCard label={t('gridPhysics.observability')} value={pct(estimate.observabilityPct, 1)} />
          </div>
          <Meter label={t('gridPhysics.observability')} value={estimate.observabilityPct} max={100} />
          <ul className="max-h-80 space-y-1 overflow-auto">
            {estimate.quantities.slice(0, 60).map((entry, index) => (
              <li
                key={`${entry.assetCode}-${entry.quantity}-${index}`}
                data-origin={entry.origin}
                className="flex flex-wrap items-baseline justify-between gap-2 rounded-md border border-border bg-surface-2/40 px-2.5 py-1 text-[11px]"
              >
                <span className="font-mono">
                  {entry.assetCode} · {entry.quantity}
                </span>
                <span className="flex items-center gap-2">
                  <span className="font-mono tabular-nums">
                    {n(entry.value, { maximumFractionDigits: 2 })} {entry.unit}
                  </span>
                  <Badge tone={entry.origin === 'measured' ? 'brand' : 'muted'}>
                    {entry.origin === 'measured' ? t('gridPhysics.measured') : t('gridPhysics.estimated')}
                  </Badge>
                  {entry.sensorCode ? (
                    <span className="font-mono text-[10px] text-text-faint">{entry.sensorCode}</span>
                  ) : null}
                </span>
              </li>
            ))}
          </ul>
        </PanelBody>
      </Panel>
    )
  }
}
