import { FormEvent, useEffect, useMemo, useState } from "react";

type Entity = {
  id: string;
  type: string;
  name: string;
  attributes: Record<string, unknown>;
};

type Action = {
  actorId: string;
  type: string;
  targetId?: string;
  locationId?: string;
};

type ValidationResult = {
  actionIndex: number;
  success: boolean;
  reason?: string;
  message?: string;
};

type Turn = {
  id: string;
  rawText: string;
  actions: Action[];
  validation: ValidationResult[];
  createdAt: number;
  interpreter?: {
    interpreterVersion: string;
    resolutionSource: "deterministic" | "llm" | "hybrid";
    minConfidence: number;
    status: "resolved" | "needs_clarification" | "rejected";
    selectedConfidence?: number;
    candidates?: Array<{
      action: Action;
      confidence: number;
      rationale?: string;
    }>;
    diagnostics: string[];
    clarification?: {
      reasonCode: string;
      question: string;
      field?: string;
      options?: Array<{
        id: string;
        label: string;
        value: string;
      }>;
    };
  };
};

type WorldState = {
  id: string;
  entities: Record<string, Entity>;
  metadata: Record<string, unknown>;
  createdAt: number;
};

type AppSnapshot = {
  worldState: WorldState;
  turns: Turn[];
};

type ProcessTurnResponse = {
  rawText: string;
  actions: Action[];
  validation: ValidationResult[];
  worldState: WorldState;
  interpreter: {
    interpreterVersion: string;
    resolutionSource: "deterministic" | "llm" | "hybrid";
    minConfidence: number;
    status: "resolved" | "needs_clarification" | "rejected";
    selectedConfidence?: number;
    candidates: Array<{
      action: Action;
      confidence: number;
      rationale?: string;
    }>;
    diagnostics: string[];
    clarification?: {
      reasonCode: string;
      question: string;
      field?: string;
      options?: Array<{
        id: string;
        label: string;
        value: string;
      }>;
    };
  };
};

type RuleCheck = {
  id: string;
  description: string;
  condition: unknown;
  failReason: string;
  failMessage: string;
};

type ActionRuleSet = {
  actionType: string;
  enabled: boolean;
  checks: RuleCheck[];
};

type SceneRulebook = {
  id: string;
  worldId: string;
  version: number;
  name: string;
  description?: string;
  rules: ActionRuleSet[];
  createdAt: number;
  updatedAt: number;
};

type RulebookListItem = {
  id: string;
  name: string;
  version: number;
  updatedAt: number;
};

const starterPrompts = [
  "look around",
  "take key",
  "take lantern",
  "give key to groundskeeper",
  "introduce jeff",
  "describe groundskeeper as patient",
  "open door",
  "move to exit",
];

function formatConfidence(value: number | undefined): string {
  if (typeof value !== "number") return "n/a";
  return `${Math.round(value * 100)}%`;
}

function formatTurnTime(epochMs: number): string {
  try {
    return new Date(epochMs).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit" });
  } catch {
    return String(epochMs);
  }
}

async function fetchJson<T>(input: RequestInfo, init?: RequestInit): Promise<T> {
  const response = await fetch(input, init);
  if (!response.ok) {
    throw new Error(`Request failed: ${response.status}`);
  }
  return response.json() as Promise<T>;
}

// ---------------------------------------------------------------------------
// Rulebook editor component
// ---------------------------------------------------------------------------

function RulebookEditor(props: { onSaved?: (rulebook: SceneRulebook) => void }) {
  const [rulebook, setRulebook] = useState<SceneRulebook | null>(null);
  const [drafts, setDrafts] = useState<Record<string, string>>({});
  const [parseErrors, setParseErrors] = useState<Record<string, string>>({});
  const [saving, setSaving] = useState(false);
  const [saveError, setSaveError] = useState<string | null>(null);
  const [saved, setSaved] = useState(false);

  useEffect(() => {
    void fetchJson<SceneRulebook>("/api/rulebook").then((rb) => {
      setRulebook(rb);
      const initial: Record<string, string> = {};
      for (const ruleSet of rb.rules) {
        initial[ruleSet.actionType] = JSON.stringify(ruleSet.checks, null, 2);
      }
      setDrafts(initial);
    });
  }, []);

  function toggleEnabled(actionType: string) {
    if (!rulebook) return;
    setRulebook({
      ...rulebook,
      rules: rulebook.rules.map((r) =>
        r.actionType === actionType ? { ...r, enabled: !r.enabled } : r
      ),
    });
  }

  function updateDraft(actionType: string, value: string) {
    setDrafts((prev) => ({ ...prev, [actionType]: value }));
    setParseErrors((prev) => {
      const next = { ...prev };
      delete next[actionType];
      return next;
    });
  }

  async function saveRulebook() {
    if (!rulebook) return;
    setSaveError(null);
    setSaved(false);

    // Validate and apply all drafts.
    const updatedRules: ActionRuleSet[] = [];
    const newErrors: Record<string, string> = {};

    for (const ruleSet of rulebook.rules) {
      const draft = drafts[ruleSet.actionType] ?? JSON.stringify(ruleSet.checks, null, 2);
      try {
        const parsedChecks = JSON.parse(draft) as RuleCheck[];
        updatedRules.push({ ...ruleSet, checks: parsedChecks });
      } catch {
        newErrors[ruleSet.actionType] = "Invalid JSON";
        updatedRules.push(ruleSet);
      }
    }

    if (Object.keys(newErrors).length > 0) {
      setParseErrors(newErrors);
      return;
    }

    setSaving(true);
    try {
      const updated = await fetchJson<SceneRulebook>("/api/rulebook", {
        method: "PUT",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ ...rulebook, rules: updatedRules }),
      });
      setRulebook(updated);
      props.onSaved?.(updated);
      setSaved(true);
      setTimeout(() => setSaved(false), 2500);
    } catch (err) {
      setSaveError(err instanceof Error ? err.message : "Save failed");
    } finally {
      setSaving(false);
    }
  }

  if (!rulebook) return <p>Loading rulebook…</p>;

  return (
    <div className="rulebook-editor">
      <div className="rulebook-header">
        <div>
          <p className="rulebook-name">{rulebook.name}</p>
          <p className="rulebook-desc">
            Version {rulebook.version} | Updated {formatTurnTime(rulebook.updatedAt)}
          </p>
          {rulebook.description ? (
            <p className="rulebook-desc">{rulebook.description}</p>
          ) : null}
        </div>
        <button
          type="button"
          onClick={() => void saveRulebook()}
          disabled={saving}
          className="save-btn"
        >
          {saving ? "Saving…" : saved ? "Saved" : "Save rulebook"}
        </button>
      </div>

      {saveError ? <p className="error-banner">{saveError}</p> : null}

      <div className="rule-list">
        {rulebook.rules.map((ruleSet) => (
          <details key={ruleSet.actionType} className="rule-panel">
            <summary className="rule-summary">
              <span className="rule-action-type">{ruleSet.actionType}</span>
              <span className="rule-check-count">{ruleSet.checks.length} check{ruleSet.checks.length !== 1 ? "s" : ""}</span>
              <label
                className="rule-toggle"
                onClick={(e) => e.stopPropagation()}
              >
                <input
                  type="checkbox"
                  checked={ruleSet.enabled}
                  onChange={() => toggleEnabled(ruleSet.actionType)}
                />
                {ruleSet.enabled ? "enforced" : "disabled"}
              </label>
            </summary>

            <div className="rule-body">
              <p className="rule-hint">
                Edit the checks array below. Each check has: <code>id</code>, <code>description</code>, <code>condition</code>, <code>failReason</code>, <code>failMessage</code>.
              </p>
              <p className="rule-hint">
                For character permissions, use <code>{`{"op":"actorIdIn","allowedIds":["player"]}`}</code> or <code>{`{"op":"actorNameIn","allowedNames":["Player"]}`}</code>.
              </p>
              <p className="rule-hint">
                For conditional creation, use <code>{`{"op":"actionMetadataEq","key":"createIfMissing","value":true}`}</code> together with actor checks.
              </p>
              <p className="rule-hint">
                For transfer ownership checks, use <code>{`{"op":"itemInInventory","itemMetadataKey":"itemId","holderRole":"actor"}`}</code>.
              </p>
              {ruleSet.checks.length > 0 ? (
                <ul className="check-list">
                  {ruleSet.checks.map((check) => (
                    <li key={check.id} title={check.failReason}>
                      <span className="check-desc">{check.description}</span>
                    </li>
                  ))}
                </ul>
              ) : (
                <p className="rule-hint">No checks — action always passes.</p>
              )}
              <label className="json-label">
                Checks JSON
                <textarea
                  className={`json-editor${parseErrors[ruleSet.actionType] ? " json-error" : ""}`}
                  value={drafts[ruleSet.actionType] ?? ""}
                  onChange={(e) => updateDraft(ruleSet.actionType, e.target.value)}
                  rows={10}
                  spellCheck={false}
                />
              </label>
              {parseErrors[ruleSet.actionType] ? (
                <p className="error-banner">{parseErrors[ruleSet.actionType]}</p>
              ) : null}
            </div>
          </details>
        ))}
      </div>
    </div>
  );
}

// ---------------------------------------------------------------------------
// Main app
// ---------------------------------------------------------------------------

export default function App() {
  const [snapshot, setSnapshot] = useState<AppSnapshot | null>(null);
  const [latest, setLatest] = useState<ProcessTurnResponse | null>(null);
  const [input, setInput] = useState("look around");
  const [loading, setLoading] = useState(true);
  const [submitting, setSubmitting] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [tab, setTab] = useState<"world" | "rulebook">("world");
  const [activeRulebook, setActiveRulebook] = useState<SceneRulebook | null>(null);
  const [rulebooks, setRulebooks] = useState<RulebookListItem[]>([]);

  async function refreshRulebookState() {
    const [active, list] = await Promise.all([
      fetchJson<SceneRulebook>("/api/rulebook"),
      fetchJson<RulebookListItem[]>("/api/rulebooks"),
    ]);
    setActiveRulebook(active);
    setRulebooks(list);
  }

  useEffect(() => {
    void Promise.all([fetchJson<AppSnapshot>("/api/state"), refreshRulebookState()])
      .then(([data]) => {
        setSnapshot(data);
        setLoading(false);
      })
      .catch((fetchError: Error) => {
        setError(fetchError.message);
        setLoading(false);
      });
  }, []);

  async function onSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();
    setSubmitting(true);
    setError(null);

    try {
      const result = await fetchJson<ProcessTurnResponse>("/api/turn", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ input }),
      });
      setLatest(result);
      const [nextSnapshot] = await Promise.all([
        fetchJson<AppSnapshot>("/api/state"),
        refreshRulebookState(),
      ]);
      setSnapshot(nextSnapshot);
    } catch (submitError) {
      setError(submitError instanceof Error ? submitError.message : "Unknown error");
    } finally {
      setSubmitting(false);
    }
  }

  async function onReset() {
    setError(null);
    try {
      const result = await fetchJson<AppSnapshot>("/api/reset", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({}),
      });
      setSnapshot(result);
      setLatest(null);
      await refreshRulebookState();
    } catch (resetError) {
      setError(resetError instanceof Error ? resetError.message : "Unknown error");
    }
  }

  const entities = snapshot ? Object.values(snapshot.worldState.entities) : [];
  const turns = useMemo(() => snapshot?.turns.slice().reverse() ?? [], [snapshot]);

  function applyClarificationOption(optionValue: string) {
    setInput((prev) => {
      const trimmed = prev.trim();
      if (!trimmed) return optionValue;
      return `${trimmed} ${optionValue}`;
    });
  }

  return (
    <main className="page-shell">
      <section className="hero-panel">
        <p className="eyebrow">CharacterGarden</p>
        <h1>Bootable narrative sandbox</h1>
        <p className="lede">
          Submit a turn, inspect world state, and verify how the truth engine is mutating state.
        </p>

        <form className="turn-form" onSubmit={onSubmit}>
          <label htmlFor="turn-input">Turn input</label>
          <textarea
            id="turn-input"
            value={input}
            onChange={(event) => setInput(event.target.value)}
            rows={4}
            placeholder="Type what the player does..."
          />
          <div className="actions-row">
            <button type="submit" disabled={submitting}>
              {submitting ? "Submitting..." : "Run turn"}
            </button>
            <button type="button" className="chip" onClick={onReset}>
              Reset world
            </button>
            <div className="chips">
              {starterPrompts.map((prompt) => (
                <button key={prompt} type="button" className="chip" onClick={() => setInput(prompt)}>
                  {prompt}
                </button>
              ))}
            </div>
          </div>
        </form>

        {latest ? (
          <section className="result-card">
            <h2>Latest result</h2>
            <p><strong>Input:</strong> {latest.rawText}</p>
            <p>
              <strong>Interpreter:</strong> {latest.interpreter.status}
              {` via ${latest.interpreter.resolutionSource}`}
              {` | model threshold ${formatConfidence(latest.interpreter.minConfidence)}`}
              {` | selected ${formatConfidence(latest.interpreter.selectedConfidence)}`}
            </p>
            <p>
              <strong>Interpreter version:</strong> {latest.interpreter.interpreterVersion}
            </p>
            {latest.interpreter.clarification ? (
              <p>
                <strong>Clarification ({latest.interpreter.clarification.reasonCode}):</strong>{" "}
                {latest.interpreter.clarification.question}
              </p>
            ) : null}
            {latest.interpreter.clarification?.options?.length ? (
              <div className="chips">
                {latest.interpreter.clarification.options.map((o) => (
                  <button
                    key={o.id}
                    type="button"
                    className="chip"
                    onClick={() => applyClarificationOption(o.value)}
                  >
                    clarify: {o.label}
                  </button>
                ))}
              </div>
            ) : null}
            <ul className="timeline-list compact">
              {latest.validation.map((v) => (
                <li key={v.actionIndex}>
                  Action {v.actionIndex}: {v.success ? "ok" : `failed (${v.reason ?? "unknown"})`}
                  {v.message ? ` - ${v.message}` : ""}
                </li>
              ))}
              {latest.validation.length === 0 ? (
                <li>
                  {latest.interpreter.status === "resolved"
                    ? "No actions parsed."
                    : "Turn requires clarification before any action can be validated."}
                </li>
              ) : null}
            </ul>
            {latest.interpreter.diagnostics.length > 0 ? (
              <p>
                <strong>Diagnostics:</strong> {latest.interpreter.diagnostics.join(" | ")}
              </p>
            ) : null}
            {latest.actions.length > 0 ? (
              <details>
                <summary>Selected actions ({latest.actions.length})</summary>
                <pre>{JSON.stringify(latest.actions, null, 2)}</pre>
              </details>
            ) : null}
            {latest.interpreter.candidates.length > 0 ? (
              <details>
                <summary>Interpreter candidates ({latest.interpreter.candidates.length})</summary>
                <ul className="timeline-list compact">
                  {latest.interpreter.candidates.map((candidate, index) => (
                    <li key={`${candidate.action.type}-${index}`}>
                      {candidate.action.type} at {formatConfidence(candidate.confidence)}
                      {candidate.rationale ? ` - ${candidate.rationale}` : ""}
                    </li>
                  ))}
                </ul>
              </details>
            ) : null}
          </section>
        ) : null}

        {error ? <p className="error-banner">{error}</p> : null}
      </section>

      <section className="inspector-grid">
        <nav className="tab-bar">
          <button
            type="button"
            className={`tab-btn${tab === "world" ? " active" : ""}`}
            onClick={() => setTab("world")}
          >
            World inspector
          </button>
          <button
            type="button"
            className={`tab-btn${tab === "rulebook" ? " active" : ""}`}
            onClick={() => setTab("rulebook")}
          >
            Rulebook
          </button>
        </nav>

        {tab === "world" ? (
          <>
            <article className="panel">
              <h2>World state</h2>
              {loading && !snapshot ? <p>Loading...</p> : null}
              {snapshot ? (
                <div className="meta-grid">
                  <p className="meta-kv"><strong>World ID:</strong> {snapshot.worldState.id}</p>
                  <p className="meta-kv"><strong>Created:</strong> {formatTurnTime(snapshot.worldState.createdAt)}</p>
                  <p className="meta-kv"><strong>Domain:</strong> {String(snapshot.worldState.metadata?.domain ?? "unknown")}</p>
                  <p className="meta-kv"><strong>World schema:</strong> {String(snapshot.worldState.metadata?.version ?? "unknown")}</p>
                </div>
              ) : null}
              <ul className="entity-list">
                {entities.map((entity) => (
                  <li key={entity.id}>
                    <strong>{entity.name}</strong> <span>{entity.type}</span>
                    <pre>{JSON.stringify(entity.attributes, null, 0)}</pre>
                  </li>
                ))}
              </ul>
            </article>

            <article className="panel">
              <h2>Turn log</h2>
              <ul className="timeline-list">
                {turns.map((turn) => (
                  <li key={turn.id}>
                    <strong>{turn.rawText}</strong>
                    <span className="turn-time"> at {formatTurnTime(turn.createdAt)}</span>
                    {turn.interpreter ? (
                      <span>
                        {" "}[interp:{turn.interpreter.status} via {turn.interpreter.resolutionSource};
                        conf {formatConfidence(turn.interpreter.selectedConfidence)}]
                      </span>
                    ) : null}
                    {turn.interpreter?.clarification ? (
                      <p className="parser-hint">
                        Clarification ({turn.interpreter.clarification.reasonCode}): {turn.interpreter.clarification.question}
                      </p>
                    ) : null}
                    {turn.interpreter?.diagnostics?.length ? (
                      <p className="turn-diagnostics">
                        Diagnostics: {turn.interpreter.diagnostics.join(" | ")}
                      </p>
                    ) : null}
                    {turn.validation.map((v) => (
                      <span key={v.actionIndex}> [{v.success ? "ok" : v.reason}]</span>
                    ))}
                  </li>
                ))}
              </ul>
            </article>

            <article className="panel">
              <h2>System</h2>
              {activeRulebook ? (
                <div className="meta-grid">
                  <p className="meta-kv"><strong>Active rulebook:</strong> {activeRulebook.name}</p>
                  <p className="meta-kv"><strong>Rulebook ID:</strong> {activeRulebook.id}</p>
                  <p className="meta-kv"><strong>Rulebook version:</strong> {activeRulebook.version}</p>
                  <p className="meta-kv"><strong>Updated:</strong> {formatTurnTime(activeRulebook.updatedAt)}</p>
                  <p className="meta-kv"><strong>Saved rulebooks:</strong> {rulebooks.length}</p>
                </div>
              ) : (
                <p>Loading rulebook info...</p>
              )}
              {rulebooks.length > 0 ? (
                <ul className="timeline-list compact">
                  {rulebooks.map((rb) => (
                    <li key={rb.id}>
                      <strong>{rb.name}</strong> ({rb.id}) v{rb.version} - updated {formatTurnTime(rb.updatedAt)}
                    </li>
                  ))}
                </ul>
              ) : null}
            </article>
          </>
        ) : (
          <article className="panel panel--full">
            <h2>Rulebook editor</h2>
            <RulebookEditor onSaved={(rulebook) => setActiveRulebook(rulebook)} />
          </article>
        )}
      </section>
    </main>
  );
}