import { useState } from "react";
import type { AnswerBody, AskResponse, Citation } from "./types";

// Same-origin, always, unless a build explicitly overrides it. Caddy routes
// /ask under ai.yokogawa.tech to ai-api (caddy/ai-routes.caddy), so the page
// and the API share an origin and the browser attaches the Authelia session
// cookie with no cross-origin handling at all.
//
// It is same-origin because an operator on cicore1 CANNOT resolve
// api.yokogawa.tech: LAN hosts resolve through the DC, which holds a pinpoint
// record for ai.yokogawa.tech and none for api. A cross-origin build loads and
// then fails every question on DNS.
//
// VITE_API_BASE remains for a tunnelled build, which points it at
// http://localhost:8001 - that has NO AUTHENTICATION in front of it, is
// reachable only through an SSH tunnel from one machine, and is not a
// deployment. The empty-string check is deliberate: the build arg defaults to
// "" (web/Dockerfile), and `??` does not catch an empty string - it read as a
// valid base and sent /ask to the origin with no route for it.
const API = import.meta.env.DEV
  ? "/api"
  : (import.meta.env.VITE_API_BASE || "");

export default function App() {
  const [question, setQuestion] = useState("");
  const [result, setResult] = useState<AskResponse | null>(null);
  const [error, setError] = useState<string | null>(null);
  const [busy, setBusy] = useState(false);
  const [showWorking, setShowWorking] = useState(true);
  // Stub mode only - the API ignores it otherwise. See api/stub.py.
  const [forcedClass, setForcedClass] = useState("");

  async function ask(event: React.FormEvent) {
    event.preventDefault();
    setBusy(true);
    setError(null);
    setResult(null);
    try {
      const response = await fetch(`${API}/ask`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        credentials: "include",
        body: JSON.stringify(
          forcedClass ? { question, question_class: forcedClass } : { question },
        ),
      });
      const body = await response.json();
      if (!response.ok) {
        setError(body?.detail?.message ?? "The request failed.");
        return;
      }
      setResult(body as AskResponse);
    } catch {
      setError("Could not reach the assistant.");
    } finally {
      setBusy(false);
    }
  }

  return (
    <main>
      <header>
        <h1>Waterloo Road Pump Station — Plant Assistant</h1>
        <p className="subtitle">
          Answers grounded in plant history and controlled documents. Not a
          control system, and not a substitute for a competent person.
        </p>
      </header>

      <form onSubmit={ask}>
        <textarea
          value={question}
          onChange={(e) => setQuestion(e.target.value)}
          placeholder="e.g. How many times did the wet well high level alarm come up last week?"
          rows={3}
        />
        <div className="row">
          <button type="submit" disabled={busy || question.trim().length < 3}>
            {busy ? "Working…" : "Ask"}
          </button>
          <label>
            <input
              type="checkbox"
              checked={showWorking}
              onChange={(e) => setShowWorking(e.target.checked)}
            />
            Show working
          </label>
          {/* Only has an effect while the API runs with NO_LLM_STUB on. There
              is no classifier without a model, so the class is chosen here
              rather than guessed - see api/stub.py for why guessing it with
              keywords would be worse than asking. */}
          <label className="forced-class">
            Class (stub mode)
            <select
              value={forcedClass}
              onChange={(e) => setForcedClass(e.target.value)}
            >
              <option value="">Classify automatically</option>
              <option value="historical">Historical</option>
              <option value="reference">Reference</option>
              <option value="procedural">Procedural</option>
              <option value="advisory">Advisory</option>
              <option value="unclear">Unclear</option>
            </select>
          </label>
        </div>
      </form>

      {error && <div className="error">{error}</div>}
      {result && <Answer result={result} showWorking={showWorking} />}
    </main>
  );
}

function Answer({ result, showWorking }: { result: AskResponse; showWorking: boolean }) {
  const a = result.answer;
  return (
    <section className="answer">
      <div className="class-chip">{result.question_class}</div>

      {/* An answer nobody generated must not look like one that was. Same
          place, same weight and the same reasoning as the fixture banner. */}
      {a.stub_mode && (
        <div className="banner stub">
          <strong>No-LLM stub mode.</strong> No language model was called. The
          class was chosen by hand, retrieval was lexical rather than semantic,
          and the prose is a fixed placeholder. Figures, citations and document
          identities are real query results; the wording around them means
          nothing.
        </div>
      )}

      {/* Fixture data must never reach a slide unlabelled. */}
      {a.used_fixture_data && (
        <div className="banner fixture">
          <strong>Fixture data.</strong> These figures come from generated test
          data standing in for the plant historian. They are not real plant
          history and must not be quoted as such.
        </div>
      )}

      {/* Procedural and Advisory carry a visible scope banner. An operator must
          not have to infer the limits of the answer from its tone. */}
      {a.scope_banner && <div className="banner scope">{a.scope_banner}</div>}

      <p className="prose">{a.answer}</p>

      {a.procedure && <Procedure procedure={a.procedure} />}
      {a.prerequisites_verbatim && a.prerequisites_verbatim.length > 0 && (
        <div className="block">
          <h3>Prerequisites, quoted from the controlled document</h3>
          <ul>
            {a.prerequisites_verbatim.map((p, i) => (
              <li key={i}><q>{p}</q></li>
            ))}
          </ul>
        </div>
      )}

      {a.evidence && a.evidence.length > 0 && <EvidenceTable body={a} />}
      {a.deferral && <p className="deferral">{a.deferral}</p>}
      {a.clarifying_question && <p className="prose">{a.clarifying_question}</p>}

      {a.citations && a.citations.length > 0 && <Citations citations={a.citations} />}

      {showWorking && <Working result={result} />}
    </section>
  );
}

function Procedure({ procedure }: { procedure: NonNullable<AnswerBody["procedure"]> }) {
  return (
    <div className="block">
      <h3>Controlled procedure</h3>
      <dl>
        <dt>Document</dt><dd>{procedure.doc_number} — {procedure.title}</dd>
        <dt>Revision</dt><dd>{procedure.revision}</dd>
        <dt>Effective</dt><dd>{procedure.effective_date ?? "not stated"}</dd>
        <dt>Authorised by</dt><dd>{procedure.authorising_role ?? "not stated"}</dd>
        <dt>Controlled copy</dt><dd>{procedure.controlled_copy_location}</dd>
      </dl>
    </div>
  );
}

function EvidenceTable({ body }: { body: AnswerBody }) {
  return (
    <div className="block">
      <h3>Evidence</h3>
      <table>
        <thead>
          <tr><th>Observation</th><th>Value</th><th>Sample size</th></tr>
        </thead>
        <tbody>
          {body.evidence!.map((e, i) => (
            <tr key={i}>
              <td>{e.description}</td>
              <td>{e.value ?? "—"} {e.unit ?? ""}</td>
              {/* A rate without its denominator invites a small sample to be
                  read as a trend. */}
              <td>{e.sample_size ?? "—"}</td>
            </tr>
          ))}
        </tbody>
      </table>
      {body.documented_limits && body.documented_limits.length > 0 && (
        <>
          <h3>Documented limits</h3>
          <ul>
            {body.documented_limits.map((l, i) => (
              <li key={i}>
                {l.description}: <strong>{l.value} {l.unit ?? ""}</strong>{" "}
                <span className="cite">
                  ({l.citation.doc_number} rev {l.citation.revision})
                </span>
              </li>
            ))}
          </ul>
        </>
      )}
    </div>
  );
}

function Citations({ citations }: { citations: Citation[] }) {
  return (
    <div className="block">
      <h3>Citations</h3>
      <ul>
        {citations.map((c, i) => (
          <li key={i}>
            <strong>{c.doc_number}</strong> — {c.title}
            {" · "}rev {c.revision}
            {" · "}effective {c.effective_date ?? "not stated"}
            {c.page !== null && <> · p.{c.page}</>}
          </li>
        ))}
      </ul>
    </div>
  );
}

function Working({ result }: { result: AskResponse }) {
  const a = result.answer;
  return (
    <details className="working" open>
      <summary>Working</summary>
      <dl>
        <dt>Class</dt><dd>{result.question_class} (confidence {result.confidence.toFixed(2)})</dd>
        {result.downgraded_reason && (<><dt>Routing</dt><dd>{result.downgraded_reason}</dd></>)}
        {a.time_window && (<><dt>Window</dt><dd>{a.time_window.description}</dd></>)}
        {a.row_count !== undefined && (<><dt>Rows</dt><dd>{a.row_count}</dd></>)}
        <dt>Latency</dt><dd>{result.latency_ms} ms</dd>
        <dt>Request</dt><dd><code>{result.request_id}</code></dd>
      </dl>
      {a.query && (
        <>
          <h4>Query</h4>
          <pre>{JSON.stringify(a.query, null, 2)}</pre>
        </>
      )}
      {a.rows && a.rows.length > 0 && (
        <>
          <h4>Rows</h4>
          <pre>{JSON.stringify(a.rows.slice(0, 25), null, 2)}</pre>
        </>
      )}
    </details>
  );
}
