'use client';

/**
 * Review-first record drawer — the fast triage surface for a status-carrying
 * entity type (bd startsim-768w.18.14 / .15).
 *
 * Opens READ-OPTIMISED, not as an edit form: the record's title / summary /
 * sources up top, then a sticky bar with ONE decision — Approve / Needs work /
 * Reject. Each decision writes BOTH underlying fields coherently (status = the
 * pipeline gate + team_verdict = the re-rank signal) in a single save, so there's
 * no separate "verdict" row duplicating Approve/Reject. Approve/Reject advance to
 * the next record; the current state shows as a badge that doubles as the rare
 * manual override (mark written, un-reject). ↑/↓ (or j/k) walk the queue; a / w /
 * x / e are bare-letter shortcuts (guarded while typing). Deep field-editing stays
 * behind "Edit fields" (the app injects its form via renderEditFields).
 *
 * Generic + client-injected: the consumer passes a CollectionClient, the ordered
 * queue (prev/next), and optional ReviewConfig / render-props. Defaults match the
 * OGMC content schema but everything is a prop — nothing here is tenant-specific.
 */
import { useEffect, useMemo, useState, type ReactNode } from 'react';
import {
  ChevronLeft,
  ChevronRight,
  Check,
  X,
  PencilLine,
  ExternalLink,
  ShieldCheck,
  MessageSquarePlus,
} from 'lucide-react';

import { Button } from '../ui/button';
import { Textarea } from '../ui/textarea';
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from '../ui/select';
import { notify } from '../toast/use-notify';
import { cn } from '../../lib/utils';
import type { CollectionClient, EntityRecord, EntityTypeDef } from './types';
import { readData, toCamelKey } from './data';
import { parseSources } from './drafts';
import {
  resolveReviewConfig,
  reviewDecisions,
  shouldIgnoreShortcut,
  fieldStr,
  reviewHostOf,
  type ReviewConfig,
  type ReviewDecision,
} from './review';

export interface ReviewDrawerProps {
  client: CollectionClient;
  type: EntityTypeDef;
  /** The ordered queue the drawer walks with prev/next (the current list/page). */
  records: EntityRecord[];
  /** The open record (null closes the drawer). */
  record: EntityRecord | null;
  onClose: () => void;
  /** Move the open record to another (prev/next / auto-advance). */
  onNavigate: (record: EntityRecord) => void;
  /** Fired after any successful save so the parent can refetch/invalidate. */
  onSaved?: () => void;
  /** Field-map + transition overrides; defaults match the OGMC content schema. */
  config?: ReviewConfig;
  /** Map of source host → tier (1 = approved) for the source chips. Optional. */
  sourceTierByHost?: Map<string, number>;
  /** The app's deep field editor, shown behind "Edit fields". Optional. */
  renderEditFields?: (ctx: {
    record: EntityRecord;
    type: EntityTypeDef;
    /** Return to review mode without saving. */
    back: () => void;
    /** Refetch the parent + return to review mode. */
    saved: () => void;
  }) => ReactNode;
  /** App-specific extra content under the record (e.g. a topic's drafts). Optional. */
  renderExtra?: (record: EntityRecord, type: EntityTypeDef) => ReactNode;
}

export function ReviewDrawer(props: ReviewDrawerProps) {
  if (!props.record) return null;
  return <ReviewDrawerInner key={props.record.id} {...props} record={props.record} />;
}

function ReviewDrawerInner({
  client,
  type,
  records,
  record,
  onClose,
  onNavigate,
  onSaved,
  config,
  sourceTierByHost,
  renderEditFields,
  renderExtra,
}: ReviewDrawerProps & { record: EntityRecord }) {
  const cfg = useMemo(() => resolveReviewConfig(type, config), [type, config]);
  const decisions = useMemo(() => reviewDecisions(cfg), [cfg]);
  const [mode, setMode] = useState<'review' | 'edit'>('review');
  // Local optimistic copy so a click reflects instantly (the parent also refetches).
  const [data, setData] = useState<Record<string, unknown>>(() => ({ ...record.data }));
  const [busy, setBusy] = useState<string | null>(null);
  const [noteOpen, setNoteOpen] = useState(false);
  const [note, setNote] = useState(() => fieldStr(record.data, cfg.noteAttr));

  const index = records.findIndex((r) => String(r.id) === String(record.id));
  const prev = index > 0 ? records[index - 1] : null;
  const next = index >= 0 && index + 1 < records.length ? records[index + 1] : null;

  const status = fieldStr(data, cfg.statusName);
  const verdict = fieldStr(data, cfg.verdictAttr);
  const title = fieldStr(data, cfg.titleAttr) || record.name || `#${record.id}`;
  const summary = fieldStr(data, cfg.summaryAttr);
  const rank = fieldStr(data, cfg.rankAttr);

  /** Merge changes into the (camelCased) blob and PATCH; the backend REPLACES data. */
  async function patch(changes: Record<string, unknown>, key: string, advance = false) {
    const nextData: Record<string, unknown> = { ...data };
    for (const [k, v] of Object.entries(changes)) {
      const camel = toCamelKey(k);
      if (v === '' || v == null) {
        delete nextData[camel];
        delete nextData[k];
      } else {
        nextData[camel] = v;
        if (camel !== k) delete nextData[k];
      }
    }
    setData(nextData); // optimistic
    setBusy(key);
    try {
      await client.updateEntity(record.id, { data: nextData });
      onSaved?.();
      if (advance && next) onNavigate(next);
    } catch (err) {
      setData({ ...record.data }); // roll back
      notify.error(err instanceof Error ? err.message : 'Could not save.');
    } finally {
      setBusy(null);
    }
  }

  /** One decision → write status AND verdict together (the coherent pair). */
  function applyDecision(d: ReviewDecision) {
    const changes: Record<string, unknown> = {};
    if (d.status) changes[cfg.statusName] = d.status;
    if (d.verdict) changes[cfg.verdictAttr] = d.verdict;
    if (d.promptNote) setNoteOpen(true);
    void patch(changes, `decision:${d.key}`, d.advance);
  }

  /** Manual state override from the badge — status only, no verdict, no advance. */
  function overrideStatus(value: string) {
    if (value === status) return;
    void patch({ [cfg.statusName]: value }, `status:${value}`, false);
  }

  async function saveNote() {
    await patch({ [cfg.noteAttr]: note.trim() }, 'note');
    setNoteOpen(false);
  }

  // Keyboard: walk the queue + one-key decide. Guarded so it never fires while typing.
  useEffect(() => {
    if (mode !== 'review') return;
    function onKey(e: KeyboardEvent) {
      if (shouldIgnoreShortcut(e)) return;
      const d = decisions.find((x) => x.shortcut === e.key);
      if (d) {
        e.preventDefault();
        applyDecision(d);
        return;
      }
      switch (e.key) {
        case 'j':
        case 'ArrowDown':
          if (next) { e.preventDefault(); onNavigate(next); }
          break;
        case 'k':
        case 'ArrowUp':
          if (prev) { e.preventDefault(); onNavigate(prev); }
          break;
        case 'e': e.preventDefault(); setMode('edit'); break;
        case 'Escape': e.preventDefault(); onClose(); break;
      }
    }
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [mode, prev, next, decisions, data]);

  const legend = [...decisions.map((d) => `${d.shortcut} ${d.label.toLowerCase()}`), 'e edit', 'j/k move'].join(' · ');

  return (
    <div className="fixed inset-0 z-50 flex justify-end" role="dialog" aria-modal="true">
      <div className="absolute inset-0 bg-black/30" onClick={onClose} aria-hidden />
      <aside className="relative z-10 flex h-full w-full max-w-2xl flex-col overflow-hidden border-l border-border bg-card shadow-xl">
        <header className="flex items-center justify-between gap-2 border-b border-border px-4 py-3">
          <div className="flex min-w-0 items-center gap-3">
            <span className="text-xs uppercase tracking-wide text-muted-foreground">{type.label}</span>
            {index >= 0 && records.length > 0 ? (
              <span className="shrink-0 text-xs text-muted-foreground">
                {index + 1} / {records.length}
              </span>
            ) : null}
          </div>
          <div className="flex shrink-0 items-center gap-1">
            {/* Current state — and the rare manual override (mark written, un-reject). */}
            {cfg.statusChoices.length > 0 ? (
              <Select value={status || undefined} onValueChange={overrideStatus}>
                <SelectTrigger className="h-7 w-auto gap-1 border-border text-xs capitalize">
                  <SelectValue placeholder="State…" />
                </SelectTrigger>
                <SelectContent>
                  {cfg.statusChoices.map((s) => (
                    <SelectItem key={s} value={s} className="capitalize">
                      {s}
                    </SelectItem>
                  ))}
                </SelectContent>
              </Select>
            ) : null}
            <button
              onClick={() => onNavigate(prev!)}
              disabled={!prev}
              className="rounded p-1 text-muted-foreground hover:bg-muted disabled:opacity-30"
              aria-label="Previous"
              title="Previous (k / ↑)"
            >
              <ChevronLeft className="h-4 w-4" />
            </button>
            <button
              onClick={() => onNavigate(next!)}
              disabled={!next}
              className="rounded p-1 text-muted-foreground hover:bg-muted disabled:opacity-30"
              aria-label="Next"
              title="Next (j / ↓)"
            >
              <ChevronRight className="h-4 w-4" />
            </button>
            <button
              onClick={() => setMode((m) => (m === 'review' ? 'edit' : 'review'))}
              className="ml-1 rounded border border-border px-2.5 py-1 text-xs hover:bg-muted"
              title="Edit fields (e)"
            >
              {mode === 'review' ? 'Edit fields' : 'Review'}
            </button>
            <button
              onClick={onClose}
              className="rounded p-1 text-muted-foreground hover:bg-muted"
              aria-label="Close"
            >
              <X className="h-4 w-4" />
            </button>
          </div>
        </header>

        {mode === 'edit' ? (
          <div className="flex-1 overflow-y-auto px-4 py-4">
            {renderEditFields ? (
              renderEditFields({
                record,
                type,
                back: () => setMode('review'),
                saved: () => {
                  onSaved?.();
                  setMode('review');
                },
              })
            ) : (
              <p className="text-sm text-muted-foreground">No field editor is wired for this type.</p>
            )}
          </div>
        ) : (
          <>
            <div className="flex-1 overflow-y-auto px-5 py-4">
              <h2 className="text-lg font-semibold leading-snug text-foreground">{title}</h2>

              <div className="mt-2 flex flex-wrap items-center gap-1.5 text-xs">
                {rank ? (
                  <span className="rounded-full bg-primary/10 px-2 py-0.5 font-semibold text-primary">
                    rank {rank}
                  </span>
                ) : null}
                {cfg.metaAttrs.map((a) => {
                  const v = fieldStr(data, a);
                  if (!v) return null;
                  return (
                    <span key={a} className="rounded-full bg-muted px-2 py-0.5 text-muted-foreground">
                      {v.replace(/_/g, ' ')}
                    </span>
                  );
                })}
              </div>

              {summary ? <p className="mt-3 text-sm leading-relaxed text-foreground/80">{summary}</p> : null}

              <ReviewSources data={data} sourceAttrs={cfg.sourceAttrs} tierByHost={sourceTierByHost} />

              {renderExtra ? <div className="mt-6 border-t border-border pt-4">{renderExtra(record, type)}</div> : null}
            </div>

            <footer className="space-y-3 border-t border-border px-4 py-3">
              {decisions.length > 0 ? (
                <div className="flex items-stretch gap-2">
                  {decisions.map((d) => (
                    <DecisionButton
                      key={d.key}
                      decision={d}
                      active={verdict !== '' && d.verdict === verdict}
                      busy={busy === `decision:${d.key}`}
                      onClick={() => applyDecision(d)}
                    />
                  ))}
                </div>
              ) : null}

              <div className="flex items-center gap-2">
                <button
                  onClick={() => setNoteOpen((o) => !o)}
                  className={cn(
                    'inline-flex items-center gap-1 rounded-md border border-border px-2.5 py-1 text-xs hover:bg-muted',
                    note && 'text-primary',
                  )}
                >
                  <MessageSquarePlus className="h-3.5 w-3.5" />
                  {note ? 'Note ✓' : 'Add note'}
                </button>
                <span className="ml-auto text-[11px] text-muted-foreground">{legend}</span>
              </div>

              {noteOpen ? (
                <div className="space-y-2">
                  <Textarea
                    value={note}
                    onChange={(e) => setNote(e.target.value)}
                    rows={2}
                    placeholder="Why? — e.g. “good topic, angle too broad; needs a fresher source.”"
                    className="text-sm"
                    autoFocus
                  />
                  <div className="flex gap-2">
                    <Button size="sm" onClick={saveNote} disabled={busy === 'note'}>
                      {busy === 'note' ? 'Saving…' : 'Save note'}
                    </Button>
                    <Button
                      size="sm"
                      variant="outline"
                      onClick={() => {
                        setNote(fieldStr(data, cfg.noteAttr));
                        setNoteOpen(false);
                      }}
                    >
                      Cancel
                    </Button>
                  </div>
                </div>
              ) : note ? (
                <p className="border-l-2 border-border pl-2 text-xs italic text-muted-foreground">{note}</p>
              ) : null}
            </footer>
          </>
        )}
      </aside>
    </div>
  );
}

const DECISION_ICON: Record<ReviewDecision['tone'], typeof Check> = {
  good: Check,
  bad: X,
  neutral: PencilLine,
};
const DECISION_STYLE: Record<ReviewDecision['tone'], string> = {
  good: 'bg-emerald-600 text-white hover:bg-emerald-700 border-emerald-600',
  bad: 'border-rose-300 text-rose-700 hover:bg-rose-50',
  neutral: 'border-amber-300 text-amber-700 hover:bg-amber-50',
};

function DecisionButton({
  decision,
  active,
  busy,
  onClick,
}: {
  decision: ReviewDecision;
  active: boolean;
  busy: boolean;
  onClick: () => void;
}) {
  const Icon = DECISION_ICON[decision.tone];
  return (
    <button
      type="button"
      onClick={onClick}
      disabled={busy}
      title={`${decision.label} (${decision.shortcut})`}
      className={cn(
        'flex flex-1 flex-col items-center justify-center gap-0.5 rounded-md border px-3 py-2 text-sm font-medium transition disabled:opacity-60',
        DECISION_STYLE[decision.tone],
        active && 'ring-2 ring-offset-1 ring-current',
      )}
    >
      <span className="flex items-center gap-1.5">
        <Icon className="h-4 w-4" />
        {busy ? 'Saving…' : decision.label}
      </span>
      <span className={cn('text-[10px] font-normal', decision.tone === 'good' ? 'text-white/80' : 'opacity-70')}>
        {decisionHint(decision)}
      </span>
    </button>
  );
}

/** The tiny "→ ready · good" outcome hint under a decision. */
function decisionHint(d: ReviewDecision): string {
  const parts = [d.status ? `→ ${d.status}` : null, d.verdict].filter(Boolean);
  return parts.join(' · ');
}

/** Source chips: host + optional tier badge, linked when a url is present. */
function ReviewSources({
  data,
  sourceAttrs,
  tierByHost,
}: {
  data: Record<string, unknown>;
  sourceAttrs: string[];
  tierByHost?: Map<string, number>;
}) {
  const urls = useMemo(() => {
    const out: string[] = [];
    for (const attr of sourceAttrs) {
      const raw = readData(data, attr);
      if (raw == null || raw === '') continue;
      if (attr === 'sources' || Array.isArray(raw)) {
        for (const s of parseSources(raw)) {
          const u = (s.url || '').trim();
          if (u) out.push(u);
        }
      } else {
        out.push(String(raw).trim());
      }
    }
    return out.filter(Boolean);
  }, [data, sourceAttrs]);

  if (sourceAttrs.length === 0) return null;
  if (urls.length === 0) {
    return (
      <p className="mt-3 text-xs font-medium text-amber-600">No sources — needs verification.</p>
    );
  }
  return (
    <div className="mt-3 flex flex-wrap items-center gap-1.5">
      <span className="text-xs text-muted-foreground">Sources:</span>
      {urls.map((u, i) => {
        const host = reviewHostOf(u);
        const tier = host ? tierByHost?.get(host) : undefined;
        const href = /^https?:\/\//.test(u) ? u : host ? `https://${u}` : undefined;
        const chip = (
          <span className="inline-flex items-center gap-1 rounded-full border border-border bg-card px-2 py-0.5 text-xs text-foreground/80">
            {tier === 1 ? <ShieldCheck className="h-3 w-3 text-emerald-600" /> : null}
            <span className="max-w-[12rem] truncate">{host || u}</span>
            {tier != null ? (
              <span
                className={cn(
                  'rounded px-1 text-[10px] font-semibold',
                  tier === 1 ? 'bg-emerald-100 text-emerald-700' : 'bg-muted text-muted-foreground',
                )}
              >
                T{tier}
              </span>
            ) : null}
            {href ? <ExternalLink className="h-3 w-3 text-muted-foreground" /> : null}
          </span>
        );
        return href ? (
          <a key={i} href={href} target="_blank" rel="noreferrer" className="hover:opacity-80">
            {chip}
          </a>
        ) : (
          <span key={i}>{chip}</span>
        );
      })}
    </div>
  );
}
