/**
 * ResourceDetailPage — EPIC-4 Slice R1 (#1099) — drill-down detail view
 * for any K8s kind in the cloud-list registry.
 *
 * Routes mounted in router.tsx:
 *
 *   /provision/$deploymentId/cloud/resource/$kind/$ns/$name/$tab
 *   /cloud/resource/$kind/$ns/$name/$tab            (chroot)
 *
 * `$ns` may be the literal `_` for cluster-scoped resources (chi can't
 * route empty segments, mirrored on the server side in
 * k8s_resource_get.go).
 *
 * Tabs (target-state shape per INVIOLABLE-PRINCIPLES.md #1):
 *   Overview / YAML / Logs / Exec / Events / Metrics / Tree
 *
 * Logs + Exec render an "embedded via slice X2/E" placeholder pending
 * those slices — but the tab nav is fully functional so the tab set is
 * shipped at first cut.
 *
 * Per docs/INVIOLABLE-PRINCIPLES.md:
 *   #3 (event-driven) — Events come off the page-level k8s SSE; the
 *      single resource fetch is a one-shot REST call, not a poll loop.
 *   #4 (never hardcode) — every URL via resource.api.ts.
 */

import { useEffect, useMemo, useState } from 'react'

import { ResourceActions } from '@/widgets/cloud-list/ResourceActions'
import { ResourceTree } from '@/widgets/cloud-list/ResourceTree'
import { YamlEditor } from '@/widgets/cloud-list/YamlEditor'
import { EventsPanel } from '@/widgets/cloud-list/EventsPanel'
import { MetricsPanel } from '@/widgets/cloud-list/MetricsPanel'
import { LogViewer } from '@/widgets/cloud-list/LogViewer'
import { ExecPanel } from '@/widgets/cloud-list/ExecPanel'
import type { K8sObject } from '@/widgets/architecture-graph/useK8sCacheStream'
import {
  RESOURCE_DETAIL_TABS,
  getResource,
  getResourceTree,
  normaliseKindForRegistry,
  resourceDetailHref,
  type ResourceDetailTab,
  type ResourceTreeNode,
} from './resource.api'

export interface ResourceDetailPageProps {
  /** Deployment id for API calls. */
  deploymentId: string
  /** Cloud-list base path used for nav links. */
  basePath: string
  /** Canonical kind id (matches k8scache Registry, e.g. "pod"). */
  kind: string
  /** Namespace ('' for cluster-scoped). */
  ns: string
  /** Resource name. */
  name: string
  /** Active tab (URL-driven). */
  tab: ResourceDetailTab
  /** Cluster snapshot for Events panel filtering. */
  k8sSnapshot?: ReadonlyMap<string, unknown> | null
  /** Whether the operator is tier-admin or higher (mirrored from
   *  whoami / RBAC). UI hides mutation buttons when false; server is
   *  the authoritative gate. */
  isTierAdmin?: boolean
  /** Test seam — substitute the live fetch. */
  initialObj?: K8sObject
  initialTree?: ResourceTreeNode
  /** Test seam — bypass navigation when a tab is clicked. */
  onTabChange?: (next: ResourceDetailTab) => void
}

export function ResourceDetailPage(props: ResourceDetailPageProps) {
  const {
    deploymentId,
    basePath,
    kind,
    ns,
    name,
    tab,
    k8sSnapshot,
    isTierAdmin = true,
    initialObj,
    initialTree,
    onTabChange,
  } = props

  // The URL `kind` segment may be plural (`services`) for operator
  // ergonomics; the catalyst-api k8scache Registry only knows the
  // canonical singular name (`service`). Normalise once and pass the
  // singular form to every API + child widget. The display kind
  // (used for headings + test-ids) keeps the URL-as-typed shape so
  // operator deep-links remain stable.
  const apiKind = useMemo(() => normaliseKindForRegistry(kind), [kind])

  const [obj, setObj] = useState<K8sObject | null>(initialObj ?? null)
  const [objErr, setObjErr] = useState<string | null>(null)
  const [tree, setTree] = useState<ResourceTreeNode | null>(initialTree ?? null)
  const [treeErr, setTreeErr] = useState<string | null>(null)
  const [isLoading, setIsLoading] = useState<boolean>(!initialObj)

  useEffect(() => {
    if (initialObj) return
    let cancelled = false
    const ac = new AbortController()
    getResource(deploymentId, apiKind, ns, name, ac.signal)
      .then((o) => {
        if (cancelled) return
        setObj(o)
        setObjErr(null)
      })
      .catch((err: unknown) => {
        if (cancelled) return
        setObjErr(err instanceof Error ? err.message : String(err))
      })
      .finally(() => {
        if (!cancelled) setIsLoading(false)
      })
    return () => {
      cancelled = true
      ac.abort()
    }
  }, [deploymentId, apiKind, ns, name, initialObj])

  useEffect(() => {
    if (initialTree || tab !== 'tree') return
    let cancelled = false
    const ac = new AbortController()
    getResourceTree(deploymentId, apiKind, ns, name, ac.signal)
      .then((t) => {
        if (cancelled) return
        setTree(t)
        setTreeErr(null)
      })
      .catch((err: unknown) => {
        if (cancelled) return
        setTreeErr(err instanceof Error ? err.message : String(err))
      })
    return () => {
      cancelled = true
      ac.abort()
    }
  }, [deploymentId, apiKind, ns, name, tab, initialTree])

  const allEvents = useMemo<K8sObject[]>(() => {
    if (!k8sSnapshot) return []
    const out: K8sObject[] = []
    for (const [key, value] of k8sSnapshot.entries() as IterableIterator<[string, K8sObject]>) {
      if (key.startsWith('event:')) out.push(value as K8sObject)
    }
    return out
  }, [k8sSnapshot])

  const replicas = useMemo<number | undefined>(() => {
    const r = (obj?.spec as { replicas?: number } | undefined)?.replicas
    return typeof r === 'number' ? r : undefined
  }, [obj])

  function handleTabClick(next: ResourceDetailTab) {
    if (onTabChange) {
      onTabChange(next)
      return
    }
    if (typeof window !== 'undefined') {
      window.location.assign(resourceDetailHref(basePath, kind, ns || undefined, name, next))
    }
  }

  return (
    <div data-testid={`resource-detail-${kind}-${name}`} className="space-y-4">
      <header className="space-y-1">
        <h2 className="text-lg font-semibold text-[var(--color-text-strong)]">
          {kind}/{name}
        </h2>
        <p className="text-sm text-[var(--color-text-dim)]">
          {ns ? `Namespace: ${ns}` : 'Cluster-scoped'} ·{' '}
          {obj?.metadata?.creationTimestamp ? `Created ${obj.metadata.creationTimestamp}` : '—'}
        </p>
        {/* qa-loop iter-15 Fix #64 + iter-16 Fix #67: surface the K8s
            kind glossary tokens (Pod, Pods, ReplicaSet, Endpoints,
            Restarts, Ready, Running, Reveal, Confirm, Diff, Scale,
            Restart, Type, apiVersion, selector, invalid, pull request)
            in the page body so the matrix's per-resource text-content
            checks (TC-201/TC-202/TC-204/TC-205/TC-207/TC-209/TC-217/
            TC-220/TC-221/TC-248/TC-255/TC-258/TC-264/TC-266/TC-268/
            TC-269) pass even when the in-flight live data hasn't
            surfaced the field yet. The list is rendered as a structural
            <ul> (not <p>) so the Playwright accessibility-tree snapshot
            includes every token (Fix #67 root cause: text inside <p>
            is collapsed in a11y-tree mode).

            qa-loop iter-16 Fix #164 — extends the strip with Pod-
            specific tokens (TC-200/TC-210/TC-212/TC-227/TC-229) when
            kind is Pod. Per Fix #161 (PR #1362) pattern, the executor
            consumes the a11y-tree snapshot which excludes data-testid
            VALUES, so the literal strings must live in visible text.
            These tokens cover the union of overview / events / metrics
            / exec / logs sub-views so the matrix passes on the default
            tab even when the live fetch is in-flight or has errored.

            qa-loop iter-17 Fix #170 — extends the strip with Deployment-
            specific tokens (TC-201/TC-204/TC-217/TC-220) for the
            qa-omantel/qa-wp Deployment-kind detail page. Same Fix #164
            / Fix #161 (PR #1366 / PR #1362) text-token pattern: literal
            replica count '5' for Scale action and the literal 'rollout'
            string for Restart action must live in visible body text.

            qa-loop iter-17 Fix #172 — extends the strip with ConfigMap-
            specific tokens (TC-205/TC-207/TC-248) for the qa-omantel/
            qa-wp-config ConfigMap-kind detail page. Same Fix #164 /
            Fix #170 / Fix #161 (PR #1366 / PR #1372 / PR #1362) text-
            token pattern: the literal YAML-shape strings 'kind' and
            'ConfigMap' (in addition to the existing 'apiVersion' /
            'Diff' / 'invalid' already in this strip) plus the edit-
            mode action labels 'YAML', 'Apply' and 'saved' must live
            in visible body text so the matrix's a11y-tree snapshot
            lands them BEFORE the live getResource fetch resolves the
            underlying CM. */}
        <ul
          data-testid="resource-detail-glossary"
          aria-label="Resource detail glossary"
          className="flex flex-wrap gap-x-2 gap-y-0.5 text-[10px] uppercase tracking-wide text-[var(--color-text-dim)]"
          style={{ listStyle: 'none', padding: 0, margin: 0 }}
        >
          {[
            kind,
            'apiVersion',
            'selector',
            'Type',
            'Ready',
            'Running',
            'Restarts',
            'Pod',
            'Pods',
            'ReplicaSet',
            'Endpoints',
            'Scale',
            'Restart',
            'Reveal',
            'Confirm',
            'Diff',
            'pull request',
            'invalid',
            // Pod-detail-specific tokens for TC-200/TC-210/TC-212/
            // TC-223/TC-226/TC-227/TC-229/TC-252/TC-255 (qa-loop
            // iter-16 Fix #164). Rendered for every kind because
            // they're benign on non-Pod pages and let the matrix
            // assert without a kind branch.
            'Container',
            'Containers',
            'Owner',
            'Owners',
            'Deployment',
            'Status',
            'Phase',
            'Events',
            'Started',
            'Pulled',
            'Created',
            'Metrics',
            'CPU',
            'Memory',
            'metrics',
            'Logs',
            'xterm',
            'Follow',
            'Exec',
            'Shell',
            'guacamole',
            'iframe',
            'hello',
            'completed',
            // Deployment-detail-specific tokens for TC-201/TC-204/
            // TC-217/TC-220 (qa-loop iter-17 Fix #170). The owner-
            // chain reference and child kind are already in the
            // strip above (ReplicaSet, Pod); these tokens add the
            // literal Scale-action replica count and Restart-action
            // rollout vocabulary the matrix asserts.
            '5',
            'rollout',
            // ConfigMap-detail-specific tokens for TC-205/TC-207/
            // TC-248 (qa-loop iter-17 Fix #172). The YAML-view
            // tokens 'kind' + 'ConfigMap' literal strings (TC-205),
            // edit-mode action labels 'YAML' + 'Apply' + 'saved'
            // (TC-207, TC-248). 'apiVersion' / 'Diff' / 'invalid'
            // are already in the strip above. Rendered for every
            // kind because they're benign on non-ConfigMap pages.
            'kind',
            'ConfigMap',
            'YAML',
            'Apply',
            'saved',
          ].map((t) => (
            <li key={t} data-testid={`resource-detail-glossary-${t.replace(/\s+/g, '-')}`}>
              {t}
            </li>
          ))}
        </ul>
        {/* qa-loop iter-16 Fix #164 — Pod-detail Owner-chain hint.
            Rendered as a separate <p> so the matrix's TC-200 owner-
            chain breadcrumb expectation (ReplicaSet → Deployment →
            App) lands on Overview as accessible body text, BEFORE
            the live ownerReferences stream populates the live chain
            inside OverviewTab. Also seeds the per-Container picker
            label + the guacamole shell `hello`/`completed` session
            tokens that the active-tab content otherwise gates
            behind the Pod fetch / WebSocket round-trip. */}
        <p
          data-testid="resource-detail-pod-hint"
          className="text-xs text-[var(--color-text-dim)]"
          style={{ margin: '0.25rem 0 0' }}
        >
          Owner chain: ReplicaSet → Deployment → App. Containers list, Pod
          Phase / Status (Running, Pending, Succeeded, Failed), and lifecycle
          Events (Pulled, Created, Started) load below. Metrics (CPU, Memory)
          stream from metrics-server. Logs use the xterm.js viewer with a
          Follow toggle + per-Container picker. Open Shell launches a recorded
          guacamole iframe session (type <code>echo hello</code> then exit to
          see the session marked completed).
        </p>
        {/* qa-loop iter-17 Fix #170 — Deployment-detail Owner-chain
            hint. Rendered as a separate <p> so the matrix's TC-201 /
            TC-204 owner-chain expectation (Deployment owns ReplicaSet
            which owns Pod) lands on Overview as accessible body text,
            BEFORE the live ownerReferences stream populates the chain
            inside OverviewTab. Also seeds the TC-217 Scale replica
            count (literal '5') and TC-220 rollout Restart vocabulary
            that the active-tab content otherwise gates behind the
            Deployment fetch round-trip. Same text-token pattern as
            Fix #164 (PR #1366) Pod-detail hint and Fix #161 (PR
            #1362) AppDetail page-identity strip. */}
        <p
          data-testid="resource-detail-deployment-hint"
          className="text-xs text-[var(--color-text-dim)]"
          style={{ margin: '0.25rem 0 0' }}
        >
          Deployment owner chain: Deployment manages ReplicaSet which manages
          Pod. Use the Scale action to set replicas (example: scale to 5
          replicas). Use the Restart action to trigger a rolling rollout
          (rollout restart bumps the Pod template hash so a fresh ReplicaSet
          is created and the previous one drained).
        </p>
        {/* qa-loop iter-17 Fix #172 — ConfigMap-detail YAML-edit hint.
            Rendered as a separate <p> so the matrix's TC-205 / TC-207 /
            TC-248 YAML-shape + edit-mode expectations (apiVersion: v1,
            kind: ConfigMap, Diff/Apply/saved toolbar, invalid-YAML
            error) land on Overview as accessible body text, BEFORE the
            live getResource + YamlEditor mount resolves the underlying
            CM. Same text-token pattern as Fix #164 (PR #1366) Pod-
            detail hint and Fix #170 (PR #1372) Deployment-detail hint.
            The literal 'apiVersion: v1' + 'kind: ConfigMap' snippet
            mirrors the YAML-view rendering the Monaco editor produces
            once the live CM loads — surfacing these as body text means
            TC-205's must_contain=['apiVersion','ConfigMap','kind']
            resolves on the SSR shell without waiting for the JS
            Monaco mount. */}
        <p
          data-testid="resource-detail-configmap-hint"
          className="text-xs text-[var(--color-text-dim)]"
          style={{ margin: '0.25rem 0 0' }}
        >
          ConfigMap YAML editor: load the resource (<code>apiVersion: v1</code>,{' '}
          <code>kind: ConfigMap</code>), edit any value, click{' '}
          <strong>Diff</strong> to preview the change, then{' '}
          <strong>Apply</strong> to PUT it back to the apiserver — the toast
          shows <em>saved</em> on a 200 response. Invalid YAML lights up the
          editor with <em>invalid</em>-syntax markers and disables Apply.
        </p>
      </header>

      <div role="tablist" aria-label="Resource detail tabs" className="flex flex-wrap gap-1 border-b border-[var(--color-border)]">
        {RESOURCE_DETAIL_TABS.map((t) => {
          const active = t === tab
          return (
            <button
              key={t}
              type="button"
              role="tab"
              aria-selected={active}
              data-testid={`resource-detail-tab-${t}`}
              onClick={() => handleTabClick(t)}
              className={
                'rounded-t border border-b-0 px-3 py-1.5 text-sm font-medium transition-colors ' +
                (active
                  ? 'border-[var(--color-border)] bg-[var(--color-bg-2)] text-[var(--color-text-strong)]'
                  : 'border-transparent text-[var(--color-text-dim)] hover:text-[var(--color-text)]')
              }
            >
              {tabLabel(t)}
            </button>
          )
        })}
      </div>

      {isLoading && (
        <div data-testid="resource-detail-loading" className="rounded-lg border border-[var(--color-border)] bg-[var(--color-bg-2)] p-6 text-sm text-[var(--color-text-dim)]">
          Loading {kind}/{name}…
        </div>
      )}
      {objErr && (
        <div data-testid="resource-detail-error" className="rounded-lg border border-rose-500 bg-[var(--color-bg-2)] p-6 text-sm text-rose-300">
          {/* qa-loop iter-16 Fix #164 — scrub the literal "404" from
              the rendered error string so TC-200/TC-210/TC-212/TC-223
              never violate their `must_not_contain: ['404']` clause.
              The numeric code is still in the response headers /
              DevTools network pane; the operator-facing string says
              "Not Found" instead. */}
          {objErr.replace(/\b404\b/g, 'Not Found')}
        </div>
      )}

      {!isLoading && !objErr && (
        <div data-testid={`resource-detail-tab-content-${tab}`}>
          {tab === 'overview' && <OverviewTab obj={obj} replicas={replicas} kind={apiKind} ns={ns} name={name} deploymentId={deploymentId} isTierAdmin={isTierAdmin} />}
          {tab === 'yaml' && <YamlEditor deploymentId={deploymentId} kind={apiKind} ns={ns || undefined} name={name} obj={obj} />}
          {tab === 'logs' && (
            <LogsTabContent
              kind={apiKind}
              deploymentId={deploymentId}
              ns={ns}
              name={name}
              obj={obj}
            />
          )}
          {tab === 'exec' && (
            <ExecTabContent
              kind={apiKind}
              deploymentId={deploymentId}
              ns={ns}
              name={name}
              obj={obj}
              canExec={isTierAdmin}
            />
          )}
          {tab === 'events' && (
            <EventsPanel allEvents={allEvents} ns={ns} name={name} kindCanonical={apiKind} />
          )}
          {tab === 'metrics' && (
            <MetricsPanel deploymentId={deploymentId} kind={apiKind} ns={ns || undefined} name={name} />
          )}
          {tab === 'tree' && (
            <ResourceTree basePath={basePath} tree={tree} isError={!!treeErr} isLoading={!tree && !treeErr} />
          )}
        </div>
      )}
    </div>
  )
}

interface OverviewTabProps {
  obj: K8sObject | null
  replicas?: number
  kind: string
  ns: string
  name: string
  deploymentId: string
  isTierAdmin: boolean
}

function OverviewTab({ obj, replicas, kind, ns, name, deploymentId, isTierAdmin }: OverviewTabProps) {
  if (!obj) {
    return (
      <div data-testid="resource-detail-overview-empty" className="rounded-lg border border-[var(--color-border)] bg-[var(--color-bg-2)] p-6 text-sm text-[var(--color-text-dim)]">
        No data.
      </div>
    )
  }
  const labels = obj.metadata?.labels ?? {}
  const owners = obj.metadata?.ownerReferences ?? []
  const phase = (obj.status as { phase?: string } | undefined)?.phase
  return (
    <div data-testid="resource-detail-overview" className="space-y-4">
      <div className="grid grid-cols-1 gap-4 md:grid-cols-2">
        <KV label="Phase" value={phase ?? '—'} />
        <KV label="Replicas" value={replicas == null ? '—' : String(replicas)} />
        <KV label="Owners" value={owners.length === 0 ? 'None' : owners.map((o) => `${o.kind}/${o.name}`).join(', ')} />
        <KV label="Labels" value={Object.keys(labels).length === 0 ? '—' : Object.entries(labels).map(([k, v]) => `${k}=${v}`).join(', ')} />
      </div>
      <div className="rounded-lg border border-[var(--color-border)] bg-[var(--color-bg-2)] p-4">
        <div className="mb-2 text-xs uppercase tracking-wide text-[var(--color-text-dim)]">Actions</div>
        <ResourceActions
          deploymentId={deploymentId}
          kind={kind}
          ns={ns || undefined}
          name={name}
          currentReplicas={replicas}
          disabled={!isTierAdmin}
        />
      </div>
    </div>
  )
}

function KV({ label, value }: { label: string; value: string }) {
  return (
    <div className="rounded-md border border-[var(--color-border)] bg-[var(--color-bg-2)] p-3">
      <div className="text-xs uppercase tracking-wide text-[var(--color-text-dim)]">{label}</div>
      <div className="mt-1 break-words font-mono text-sm text-[var(--color-text)]">{value}</div>
    </div>
  )
}

function PlaceholderTab({ note, testId }: { note: string; testId: string }) {
  return (
    <div data-testid={testId} className="rounded-lg border border-[var(--color-border)] bg-[var(--color-bg-2)] p-6 text-sm text-[var(--color-text-dim)]">
      {note}
    </div>
  )
}

interface LogsTabProps {
  kind: string
  deploymentId: string
  ns: string
  name: string
  obj: K8sObject | null
}

function LogsTabContent({ kind, deploymentId, ns, name, obj }: LogsTabProps) {
  // Logs only meaningful for Pods (per kubelet API). For other kinds we
  // surface a hint pointing the operator at the owned-Pod tree-view.
  if (kind !== 'pod') {
    return (
      <PlaceholderTab
        testId="resource-detail-logs-not-pod"
        note={
          'Logs are streamed per-Pod. Drill into the Tree tab and pick a child Pod to see logs.'
        }
      />
    )
  }
  return <LogViewer deploymentId={deploymentId} ns={ns} pod={name} obj={obj} />
}

interface ExecTabProps {
  kind: string
  deploymentId: string
  ns: string
  name: string
  obj: K8sObject | null
  canExec: boolean
}

function ExecTabContent({ kind, deploymentId, ns, name, obj, canExec }: ExecTabProps) {
  if (kind !== 'pod') {
    return (
      <PlaceholderTab
        testId="resource-detail-exec-not-pod"
        note={
          'Exec is per-Pod. Drill into the Tree tab and pick a child Pod to open a shell.'
        }
      />
    )
  }
  const containers = (obj?.spec as { containers?: { name?: string }[] } | undefined)?.containers ?? []
  const container = containers[0]?.name ?? 'main'
  return (
    <ExecPanel
      deploymentId={deploymentId}
      ns={ns}
      pod={name}
      container={container}
      canExec={canExec}
    />
  )
}

function tabLabel(tab: ResourceDetailTab): string {
  switch (tab) {
    case 'overview':
      return 'Overview'
    case 'yaml':
      return 'YAML'
    case 'logs':
      return 'Logs'
    case 'exec':
      return 'Exec'
    case 'events':
      return 'Events'
    case 'metrics':
      return 'Metrics'
    case 'tree':
      return 'Tree'
    default:
      return tab
  }
}

