import { useEffect, useMemo, useRef, useState } from "react";
import { Link, useParams } from "react-router-dom";
import type { Arende, Handelse, TidKategori, Tillforlitlighet } from "@/felsokning/domain";
import { KUNDBESLUT_LABEL, KVALITETSKONTROLL_LABEL, TIDKATEGORI_LABEL, TILLFORLITLIGHET_LABEL, handelseRubrik } from "@/felsokning/domain";
import type { Metodik, NastaSteg } from "@/felsokning/metodik";
import { nastaSteg } from "@/felsokning/metodik";
import { fasFor, klaraFaser } from "../../../../services/gemensam/faser.mjs";
import { Fasrad } from "@/alva/komponenter";
import { Slutsatspanel } from "@/felsokning/Slutsats";
import { sammanfatta } from "../../../../services/gemensam/sammanfattning.mjs";
import { grinda } from "../../../../services/gemensam/grind.mjs";
import { PLATTFORMSVERSION, arendebeteckning, fasDefinition } from "@/alva/system";
import { versionVidAvslut } from "../../../../services/gemensam/version.mjs";
import {
  arAvslutat,
  arendeidentitet,
  brief,
  felbeskrivning,
  formateraTid,
  foton,
  videor,
  lokalFordonshistorik,
  objekt,
  overlamningstext,
  sistaAktivitet,
  tidsfordelning,
  tidsfordelningsRader,
  type HistorikArende,
} from "@/felsokning/projektioner";
import { metodikForArende, useFelsokning } from "@/felsokning/store";
import { synkroniseraArende, type SynkStatus } from "@/felsokning/synk";
import {
  aterkallaDelning,
  hamtaDelningar,
  plattformAktiv,
  skapaDelning,
  type Delning,
  type DelningsNiva,
} from "@/felsokning/plattform";
import {
  AI_RADTYP_LABEL,
  fragaAi,
  granskaUnderlag,
  lasAvInstrument,
  sammanfattaOverlamning,
  type InstrumentTolkning,
} from "@/felsokning/ai";
import { byggDemoInstrument } from "@/felsokning/demo";
import {
  ARENDETYPER,
  ECM_VERSION,
  MARKOR_FELBESKRIVNING_VERIFIERAD,
  MARKOR_INGA_TIDIGA_OBSERVATIONER,
  INGEN_ATGARD_ORSAKER,
  KUNDKANALER,
  MARKOR_TIDIGA_OBSERVATIONER_KLARA,
  aktivtRegelpaket,
  atgarder,
  atgardsforslag,
  arendetyp,
  felorsaker,
  granskaAvvikelse,
  grindGodkand,
  kvalitetsgrind,
  hamtaRegelpaket,
  kundbeslut,
  kvalitetskontroll,
  preDiagnostik,
  reproducering,
  reproduceringsText,
  sparbarhetspaket,
  underlagFinns,
  UNDANTAGSORSAKER,
} from "@/felsokning/ecm";
import { FelsokningSkal, NivaBadge, Panel, StorKnapp, TextFalt } from "@/felsokning/ui";
import { lasVideo, skalaNerFoto, tidDatum, tidKlockslag } from "@/felsokning/format";
import { IkonCheck, IkonKamera, IkonKryss, IkonLank, IkonPunkt, IkonSok, IkonVarning } from "@/felsokning/ikoner";
import { Bild, Klipp } from "@/felsokning/Bilagevisning";
import { bilageFalt } from "@/felsokning/bilagor";

const FLIKAR = [
  { id: "guide", label: "Guide" },
  { id: "logg", label: "Logg" },
  { id: "brief", label: "Brief" },
  { id: "rapport", label: "Rapport" },
] as const;

type Flik = (typeof FLIKAR)[number]["id"];

const SYNKSTATUS_LABEL: Record<SynkStatus, string> = {
  lokal: "Lokalt läge",
  synkar: "Synkar …",
  synkad: "Synkad",
  offline: "Offline",
};

// Synkar ärendet mot backend: direkt vid nya händelser och därefter var
// 15:e sekund (hämtar kollegors händelser). Utan inloggning: lokalt läge.
function useSynk(arende: Arende | undefined): SynkStatus {
  const [status, setStatus] = useState<SynkStatus>("lokal");
  const sammanfoga = useFelsokning((s) => s.sammanfoga);
  const arendeId = arende?.id;
  const antal = arende?.handelser.length ?? 0;

  useEffect(() => {
    if (!arende || !arendeId) return;
    let aktiv = true;
    const kor = async () => {
      setStatus((s) => (s === "synkad" ? "synkar" : s));
      const resultat = await synkroniseraArende(arende);
      if (!aktiv) return;
      setStatus(resultat.status);
      if (resultat.handelser) sammanfoga(arendeId, resultat.handelser);
    };
    kor();
    const timer = setInterval(kor, 15000);
    return () => {
      aktiv = false;
      clearInterval(timer);
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps -- antal driver omsynk vid nya händelser
  }, [arendeId, antal, sammanfoga]);

  return status;
}

export default function ArendeSida() {
  const { id } = useParams<{ id: string }>();
  const arende = useFelsokning((s) => (id ? s.arenden[id] : undefined));
  const laggTill = useFelsokning((s) => s.laggTill);
  const [flik, setFlik] = useState<Flik>("guide");
  const [nu, setNu] = useState(() => new Date().toISOString());
  const synkStatus = useSynk(arende);

  useEffect(() => {
    const timer = setInterval(() => setNu(new Date().toISOString()), 30000);
    // ECM Knowledge Library: färskt regelpaket från plattformen (cache/
    // standardpaket gäller offline).
    hamtaRegelpaket();
    return () => clearInterval(timer);
  }, []);

  const metodik = metodikForArende(arende);

  if (!arende || !id) {
    return (
      <FelsokningSkal rubrik="Ärendet hittades inte" tillbaka={{ till: "/felsokning", text: "Ärenden" }}>
        <p className="text-[14px] text-[#333333]">Ärendet finns inte på den här enheten.</p>
      </FelsokningSkal>
    );
  }

  const o = objekt(arende);
  const avslutat = arAvslutat(arende);
  const total = formateraTid(tidsfordelning(arende, nu).totalMs);
  // Ett ställe flyttar bilagornas innehåll ut ur händelsen. Alla
  // panelerna nedan skickar som förut med en inbäddad data-URL; här
  // laddas den upp och händelsen får i stället en referens med
  // innehållets hash. Misslyckas uppladdningen — eller finns ingen
  // server, som i lokalt läge — sparas den inbäddat precis som förut.
  // Dokumentationen får aldrig gå förlorad för att nätet ligger nere.
  const skicka = (h: Handelse) => {
    if (!("dataUrl" in h) || !h.dataUrl) {
      laggTill(id, h);
      return;
    }
    const { dataUrl, ...utan } = h;
    void bilageFalt(id, dataUrl).then((bilaga) => laggTill(id, { ...utan, ...bilaga } as Handelse));
  };

  return (
    <FelsokningSkal
      bred
      rubrik={`#${arende.nummer} ${o?.beskrivning ?? ""}`}
      tillbaka={{ till: "/felsokning", text: "Ärenden" }}
      hoger={
        <div className="text-right">
          <p className="text-[13px] font-semibold text-white">{total}</p>
          <p className="text-[10px] font-semibold uppercase tracking-wide text-[#A9C3DE]">
            {avslutat ? "Avslutat" : "Pågår"} · {SYNKSTATUS_LABEL[synkStatus]}
          </p>
        </div>
      }
    >
      {/* Ärendeidentiteten: registreras en gång, alltid synlig — det ska
          aldrig råda tvekan om vilket fordon och ärende som avses. */}
      <IdentitetsRad arende={arende} skicka={skicka} />

      {/* Klassisk trekolumnslayout på skrivbord: navigationsträd till
          vänster, arbetsyta i mitten, kontextpanel till höger. På smala
          skärmar: flikrad + en kolumn. */}
      <nav className="mb-3 grid grid-cols-4 gap-1 border border-[#C6C6C6] bg-[#F7F7F7] p-1 lg:hidden">
        {FLIKAR.map((f) => (
          <button
            key={f.id}
            onClick={() => setFlik(f.id)}
            className={`min-h-9 text-[13px] font-semibold transition-colors ${
              flik === f.id ? "bg-[#00437A] text-white" : "text-[#333333] hover:bg-[#E4E9EE]"
            }`}
          >
            {f.label}
          </button>
        ))}
      </nav>

      <div className="lg:grid lg:grid-cols-[210px_minmax(0,1fr)] lg:items-start lg:gap-3 xl:grid-cols-[210px_minmax(0,1fr)_300px]">
        <aside className="sticky top-14 hidden lg:block print:hidden">
          <VyTrad flik={flik} sattFlik={setFlik} arende={arende} metodik={metodik} />
        </aside>

        <div className="min-w-0">
          {!avslutat && <InaktivitetsBanner arende={arende} nu={nu} skicka={skicka} />}

          {flik === "guide" && (
            <GuideFlik arende={arende} metodik={metodik} avslutat={avslutat} skicka={skicka} nu={nu} />
          )}
          {flik === "logg" && <LoggFlik arende={arende} />}
          {flik === "brief" && <BriefFlik arende={arende} metodik={metodik} nu={nu} skicka={skicka} />}
          {flik === "rapport" && <RapportFlik arende={arende} metodik={metodik} nu={nu} skicka={skicka} />}
        </div>

        <aside className="sticky top-14 hidden xl:block print:hidden">
          <KontextPanel arende={arende} metodik={metodik} nu={nu} synkStatus={synkStatus} />
        </aside>
      </div>
    </FelsokningSkal>
  );
}

// Nyckel/värde-rader för identitetspanelerna (rapport + delade vyer).
function identitetsRader(rader: [string, string | undefined][]) {
  return rader
    .filter(([, varde]) => varde)
    .map(([etikett, varde]) => (
      <div key={etikett} className="flex justify-between gap-2 border-b border-[#EBEBEB] py-1 text-[13px] last:border-0">
        <span className="text-[#4A5560]">{etikett}</span>
        <span className="text-right font-medium">{varde}</span>
      </div>
    ));
}

// Ärendeidentiteten (Case Identity): kompakt rad med fordonsobjektet och
// ärendereferenserna — alltid synlig i arbetsytan. Ärendetypen väljs här
// och styr vilka dokumentationskrav ECM ställer.
function IdentitetsRad({ arende, skicka }: { arende: Arende; skicka: (h: Handelse) => void }) {
  const idn = arendeidentitet(arende);
  const typ = arendetyp(arende);
  const del = (etikett: string, varde?: string) =>
    varde ? (
      <span className="whitespace-nowrap">
        {etikett && <span className="text-[#707070]">{etikett} </span>}
        <span className="font-semibold text-[#1A1A1A]">{varde}</span>
      </span>
    ) : null;
  return (
    <div className="mb-3 flex flex-wrap items-center gap-x-4 gap-y-1 border border-[#C6C6C6] bg-[#F7F7F7] px-3 py-1.5 text-[12px] print:hidden">
      {del("AO", idn.arbetsorder)}
      {del("Claim", idn.claim)}
      {del("Skadenr", idn.skadenummer)}
      {del("", idn.beskrivning)}
      {del("Regnr", idn.identifierare)}
      {del("VIN", idn.vin)}
      {del("Miltal", idn.miltal)}
      {del("Ansvarig", idn.ansvarig)}
      <label className="ml-auto flex items-center gap-1">
        <span className="text-[#707070]">Ärendetyp</span>
        <select
          value={typ}
          disabled={idn.avslutat}
          onChange={(e) => skicka({ typ: "arendetyp_satt", arendetyp: e.target.value })}
          className="border border-[#ADADAD] bg-white px-1.5 py-0.5 text-[12px] focus:border-[#00437A] focus:outline-none"
        >
          {ARENDETYPER.map((t) => (
            <option key={t}>{t}</option>
          ))}
        </select>
      </label>
    </div>
  );
}

// Mätarställning in/ut: instrumentpanelen fotograferas, bildtolkningen
// föreslår värdet och teknikern bekräftar. Fotot blir den officiella
// mätarställningen (E2-evidens). Undantag kräver dokumenterad orsak.
function MatarstallningSteg({ lage, skicka }: { lage: "ingaende" | "utgaende"; skicka: (h: Handelse) => void }) {
  const filRef = useRef<HTMLInputElement>(null);
  const [foto, setFoto] = useState<string | null>(null);
  const [varde, setVarde] = useState("");
  const [laser, setLaser] = useState(false);
  const [demo, setDemo] = useState(false);

  const fota = async (fil: File) => {
    setLaser(true);
    const dataUrl = await skalaNerFoto(fil);
    setFoto(dataUrl);
    try {
      const res = await lasAvInstrument(dataUrl);
      const km = res?.tolkning.varden.find((v) => /mätar|odo|km|mil/i.test(`${v.beskrivning} ${v.enhet ?? ""}`)) ?? res?.tolkning.varden[0];
      if (km) setVarde(`${km.varde}${km.enhet ? ` ${km.enhet}` : ""}`);
      if (!res) {
        setDemo(true);
        setVarde(lage === "ingaende" ? "84 320 km" : "84 512 km");
      }
    } catch {
      // Avläsningen kunde inte nås — värdet anges manuellt.
    }
    setLaser(false);
  };

  return (
    <div>
      <input
        ref={filRef}
        type="file"
        accept="image/*"
        capture="environment"
        className="hidden"
        onChange={(e) => {
          const fil = e.target.files?.[0];
          if (fil) fota(fil);
          e.target.value = "";
        }}
      />
      {!foto && !laser && (
        <StorKnapp variant="sekundar" onClick={() => filRef.current?.click()}>
          <IkonKamera /> Fotografera instrumentpanelen
        </StorKnapp>
      )}
      {laser && <p className="animate-pulse py-1 text-center text-[12px] font-semibold text-[#00437A]">Systemet läser av mätarställningen …</p>}
      {foto && !laser && (
        <div className="mt-2">
          {demo && (
            <p className="mb-2 border border-[#E0C36A] bg-[#FFF8E1] p-1.5 text-[11px] font-semibold text-[#9A6700]">
              Demo-avläsning — bildtolkningen kräver inloggning. Kontrollera värdet.
            </p>
          )}
          <img src={foto} alt="Instrumentpanel" className="mb-2 max-h-40 border border-[#C6C6C6]" />
          <TextFalt label={`Mätarställning (${lage === "ingaende" ? "ingående" : "utgående"})`} varde={varde} satt={setVarde} platshallare="T.ex. 84 320 km" />
          <StorKnapp
            disabled={!varde.trim()}
            onClick={() => {
              skicka({ typ: "matarstallning", lage, varde: varde.trim(), dataUrl: foto });
              setFoto(null);
              setVarde("");
            }}
          >
            Spara mätarställning
          </StorKnapp>
        </div>
      )}
      <Undantag vidUndantag={(orsak) => skicka({ typ: "matarstallning", lage, varde: "", undantag: orsak })} />
    </div>
  );
}

// Fordonshistorik i pre-diagnostiken: tidigare ärenden på samma objekt
// hämtas automatiskt (plattformen i inloggat läge, lokala storen annars)
// med deras dokumenterade felorsaker. Teknikern kan koppla orsakskedjan
// till det aktuella ärendet med ett tryck.
function TidigareArenden({ arende, skicka }: { arende: Arende; skicka: (h: Handelse) => void }) {
  const arenden = useFelsokning((s) => s.arenden);
  const o = objekt(arende);
  const [rader, setRader] = useState<HistorikArende[]>(() =>
    o ? lokalFordonshistorik(arenden, o.identifierare, arende.id) : [],
  );
  const identifierare = o?.identifierare;

  useEffect(() => {
    if (!identifierare) return;
    let aktiv = true;
    (async () => {
      const { plattformAktiv: aktivPlattform, plattformToken, hamtaFordonshistorik } = await import("@/felsokning/plattform");
      if (!aktivPlattform() || !plattformToken()) return;
      try {
        const svar = await hamtaFordonshistorik(identifierare);
        if (aktiv) {
          setRader(
            svar
              .filter((r) => r.id !== arende.id)
              .map((r) => ({ ...r, felbeskrivning: r.felbeskrivning ?? undefined, felorsaker: r.felorsaker ?? [] })),
          );
        }
      } catch {
        // Lokal historik gäller.
      }
    })();
    return () => {
      aktiv = false;
    };
  }, [identifierare, arende.id]);

  if (rader.length === 0) return null;
  return (
    <div className="mb-2 border border-[#C6C6C6] bg-white p-2">
      <p className="mb-1 text-[11px] font-semibold uppercase tracking-wide text-[#4A5560]">
        Tidigare ärenden på {identifierare} — granska innan historiken kvitteras
      </p>
      {rader.map((rad) => (
        <div key={rad.id} className="border-b border-[#EBEBEB] py-1.5 text-[12px] last:border-0">
          <p>
            <span className="font-semibold">Ärende #{rad.nummer}</span> · {tidDatum(rad.skapad)} ·{" "}
            {rad.avslutat ? "avslutat" : "pågående"}
            {rad.felbeskrivning && <span className="text-[#4A5560]"> — ”{rad.felbeskrivning}”</span>}
          </p>
          {rad.felorsaker.map((f, i) => (
            <p key={i} className="pl-3 text-[#4A5560]">
              Felorsak: {f.avvikelse} ({f.orsaker.join(", ")})
            </p>
          ))}
          <button
            className="mt-1 text-[11px] font-semibold text-[#00437A] underline-offset-2 hover:underline"
            onClick={() =>
              skicka({
                typ: "kommentar",
                text: `Orsakskedja: kopplat till tidigare ärende #${rad.nummer}${
                  rad.felorsaker[0] ? ` — ${rad.felorsaker[0].avvikelse}` : rad.felbeskrivning ? ` — ${rad.felbeskrivning}` : ""
                }`,
              })
            }
          >
            + Koppla till orsakskedjan
          </button>
        </div>
      ))}
    </div>
  );
}

// Pre-Diagnostic Validation: ingen felsökning påbörjas förrän
// grundkontrollerna är genomförda — eller dokumenterat motiverade.
function PreDiagnostikPanel({ arende, skicka }: { arende: Arende; skicka: (h: Handelse) => void }) {
  const rader = preDiagnostik(arende);
  const rad = (id: string) => rader.find((r) => r.id === id)!;
  const [historikVal, setHistorikVal] = useState<"" | "ja" | "nej">("");
  const [historikText, setHistorikText] = useState("");
  const fb = felbeskrivning(arende);
  const harTidiga = arende.handelser.some((p) => p.handelse.typ === "observation" || p.handelse.typ === "foto");

  return (
    <Panel rubrik="Pre-diagnostik — innan felsökningen börjar">
      {rader.map((r) => (
        <p key={r.id} className="py-0.5 text-[13px]">
          <span className={r.klar ? "text-[#1E6B34]" : "text-[#8B1A1A]"}>{r.klar ? <IkonCheck /> : "☐"}</span> {r.rubrik}
          {r.varning && <span className="ml-1 text-[11px] font-semibold text-[#9A6700]"><IkonVarning /> {r.varning}</span>}
        </p>
      ))}

      {!rad("historik").klar && (
        <div className="mt-3 border-t border-[#DDDDDD] pt-2">
          <p className="mb-2 text-[12px] font-semibold uppercase tracking-wide text-[#4A5560]">
            Har fordonets historik kontrollerats? (tidigare arbeten, återkommande fel, TSB, kampanjer)
          </p>
          <TidigareArenden arende={arende} skicka={skicka} />
          <div className="mb-2 grid grid-cols-2 gap-2">
            <StorKnapp variant={historikVal === "ja" ? "primar" : "sekundar"} onClick={() => setHistorikVal("ja")}>
              Ja — kontrollerad
            </StorKnapp>
            <StorKnapp variant={historikVal === "nej" ? "fara" : "sekundar"} onClick={() => setHistorikVal("nej")}>
              Nej
            </StorKnapp>
          </div>
          {historikVal && (
            <>
              <TextFalt
                label={historikVal === "ja" ? "Relevanta tidigare arbeten (valfritt — orsakskedja)" : "Orsak till att historiken inte kontrollerats (obligatoriskt)"}
                varde={historikText}
                satt={setHistorikText}
                platshallare={historikVal === "ja" ? "T.ex. Vattenpump bytt för 8 mån sedan — misstänkt läckage samma område" : ""}
                rost
              />
              <StorKnapp
                disabled={historikVal === "nej" && !historikText.trim()}
                onClick={() => {
                  skicka({ typ: "historik_kontrollerad", kontrollerad: historikVal === "ja", kommentar: historikText.trim() || undefined });
                  setHistorikVal("");
                  setHistorikText("");
                }}
              >
                Dokumentera
              </StorKnapp>
            </>
          )}
        </div>
      )}

      {!rad("matarstallning_in").klar && (
        <div className="mt-3 border-t border-[#DDDDDD] pt-2">
          <p className="mb-2 text-[12px] font-semibold uppercase tracking-wide text-[#4A5560]">
            Ingående mätarställning — fotografera instrumentpanelen
          </p>
          <MatarstallningSteg lage="ingaende" skicka={skicka} />
        </div>
      )}

      {!rad("felbeskrivning").klar && (
        <div className="mt-3 border-t border-[#DDDDDD] pt-2">
          <p className="mb-2 text-[12px] font-semibold uppercase tracking-wide text-[#4A5560]">
            Är kundens felbeskrivning korrekt återgiven?
          </p>
          {fb && <p className="mb-2 text-[13px]">”{fb}”</p>}
          <StorKnapp variant="sekundar" onClick={() => skicka({ typ: "kommentar", text: `${MARKOR_FELBESKRIVNING_VERIFIERAD}.` })}>
            Stämmer — verifierad
          </StorKnapp>
          <p className="mt-1 text-[11px] text-[#707070]">
            Ytterligare symptom dokumenteras som separata observationer — blanda dem inte med kundens beskrivning.
          </p>
        </div>
      )}

      {!rad("tidiga_observationer").klar && (
        <div className="mt-3 border-t border-[#DDDDDD] pt-2">
          <p className="mb-2 text-[12px] font-semibold uppercase tracking-wide text-[#4A5560]">
            Något ytterligare vid mottagandet? (reparationsspår, modifieringar, skador, läckage, korrosion …)
          </p>
          <div className="grid grid-cols-2 gap-2">
            <StorKnapp variant="sekundar" onClick={() => skicka({ typ: "kommentar", text: `${MARKOR_INGA_TIDIGA_OBSERVATIONER}.` })}>
              Inga ytterligare observationer
            </StorKnapp>
            <StorKnapp
              variant="sekundar"
              disabled={!harTidiga}
              onClick={() => skicka({ typ: "kommentar", text: `${MARKOR_TIDIGA_OBSERVATIONER_KLARA}.` })}
            >
              Observationerna är dokumenterade
            </StorKnapp>
          </div>
          <p className="mt-1 text-[11px] text-[#707070]">
            Dokumentera med foto eller observation i panelen nedan — knappen låses upp när något loggats.
          </p>
        </div>
      )}
    </Panel>
  );
}

// Symptom Verification Protocol: kundens beskrivning blir aldrig ett
// konstaterat fel förrän den reproducerats — eller dokumenterats som ej
// reproducerbar med obligatorisk motivering.
function ReproduceringPanel({ skicka }: { skicka: (h: Handelse) => void }) {
  const [status, setStatus] = useState<"" | "ja" | "delvis" | "nej">("");
  const [text, setText] = useState("");
  const etikett =
    status === "ja"
      ? "Hur reproducerades felet? (förhållanden, hastighet, temperatur, belastning …)"
      : status === "delvis"
        ? "Vad kunde respektive kunde inte reproduceras?"
        : "Motivering (obligatorisk) — varför kunde felet inte reproduceras?";
  return (
    <Panel rubrik="Symptomverifiering — reproducering">
      <p className="mb-2 text-[12px] font-semibold uppercase tracking-wide text-[#4A5560]">
        Har kundens fel kunnat reproduceras?
      </p>
      <div className="mb-2 grid grid-cols-3 gap-2">
        {(["ja", "delvis", "nej"] as const).map((val) => (
          <StorKnapp key={val} variant={status === val ? "primar" : "sekundar"} onClick={() => setStatus(val)}>
            {val === "ja" ? "Ja" : val === "delvis" ? "Delvis" : "Nej"}
          </StorKnapp>
        ))}
      </div>
      {status && (
        <>
          <TextFalt label={etikett} varde={text} satt={setText} flerRad rost
            platshallare={status === "ja" ? "T.ex. Reproducerad tre gånger vid 92–108 km/h på plan väg, varm motor" : ""} />
          <StorKnapp
            disabled={!text.trim()}
            onClick={() => {
              skicka({ typ: "reproducering", status, beskrivning: text.trim() });
              setStatus("");
              setText("");
            }}
          >
            Dokumentera reproducering
          </StorKnapp>
        </>
      )}
    </Panel>
  );
}

// Felorsaksanalys (Root Cause Analysis): vad är avvikelsen, varför har
// den uppstått, vilket underlag stöder bedömningen och hur säker är den.
// Kvalitetsregeln avvisar generella formuleringar ("trasig", "defekt")
// och evidenskällor som inte finns i loggen.
function FelorsaksPanel({ arende, skicka }: { arende: Arende; skicka: (h: Handelse) => void }) {
  const dokumenterade = felorsaker(arende);
  const [oppen, setOppen] = useState(false);
  const [avvikelse, setAvvikelse] = useState("");
  const [orsaker, setOrsaker] = useState<string[]>([]);
  const [underlag, setUnderlag] = useState<string[]>([]);
  const [sakerhet, setSakerhet] = useState<Tillforlitlighet>("hog");
  const [atgard, setAtgard] = useState("");
  const [motivering, setMotivering] = useState("");
  const [ytterligare, setYtterligare] = useState("");
  const [fel, setFel] = useState("");

  const vaxla = (lista: string[], satt: (v: string[]) => void, val: string) =>
    satt(lista.includes(val) ? lista.filter((v) => v !== val) : [...lista, val]);

  const spara = () => {
    const avvikelseFel = granskaAvvikelse(avvikelse);
    if (avvikelseFel) return setFel(avvikelseFel);
    if (orsaker.length === 0) return setFel("Välj minst en orsakskategori.");
    if (underlag.length === 0) return setFel("Koppla minst en evidenskälla till bedömningen.");
    const saknat = underlag.find((k) => !underlagFinns(arende, k));
    if (saknat) return setFel(`Underlaget ”${saknat}” finns inte i ärendets logg — dokumentera det först eller välj en annan källa.`);
    if (orsaker.includes("Okänd orsak") && !motivering.trim())
      return setFel("Okänd orsak kräver en motivering till varför orsaken inte kunnat fastställas.");
    if (sakerhet !== "hog" && !ytterligare.trim())
      return setFel("Vid medel/låg säkerhet: ange vilka ytterligare kontroller som skulle stärka bedömningen.");
    if (!atgard.trim()) return setFel("Ange rekommenderad åtgärd.");
    skicka({
      typ: "felorsak",
      avvikelse: avvikelse.trim(),
      orsaker,
      underlag,
      sakerhet,
      atgard: atgard.trim(),
      motivering: motivering.trim() || undefined,
      ytterligareKontroller: ytterligare.trim() || undefined,
    });
    setOppen(false);
    setAvvikelse("");
    setOrsaker([]);
    setUnderlag([]);
    setSakerhet("hog");
    setAtgard("");
    setMotivering("");
    setYtterligare("");
    setFel("");
  };

  return (
    <Panel rubrik="Felorsaksanalys — obligatorisk före avslut">
      {dokumenterade.map((p) => {
        const h = p.handelse;
        if (h.typ !== "felorsak") return null;
        return (
          <div key={p.id} className="mb-2 border-b border-[#EBEBEB] pb-2 text-[13px] last:border-0">
            <p className="font-semibold">{h.avvikelse}</p>
            <p className="text-[#4A5560]">
              Orsak: {h.orsaker.join(", ")} · Underlag: {h.underlag.join(", ")} · Säkerhet: {TILLFORLITLIGHET_LABEL[h.sakerhet]}
            </p>
            <p>Åtgärd: {h.atgard}</p>
          </div>
        );
      })}
      {!oppen ? (
        <StorKnapp variant="sekundar" onClick={() => setOppen(true)}>
          + Dokumentera felorsak
        </StorKnapp>
      ) : (
        <div>
          <TextFalt
            label="1. Konstaterad avvikelse (inte bara komponenten)"
            varde={avvikelse}
            satt={setAvvikelse}
            flerRad
            rost
            platshallare="T.ex. Startmotorn aktiverar inte trots korrekt matningsspänning och god jordförbindelse."
          />
          <p className="mb-1 text-[11px] font-semibold uppercase tracking-wide text-[#4A5560]">2. Mest sannolik orsak (en eller flera)</p>
          <div className="mb-3 grid grid-cols-2 gap-1 sm:grid-cols-3">
            {aktivtRegelpaket().orsakskategorier.map((o) => (
              <button
                key={o}
                onClick={() => vaxla(orsaker, setOrsaker, o)}
                className={`min-h-8 border px-1.5 text-[12px] font-medium ${
                  orsaker.includes(o) ? "border-[#00437A] bg-[#D6E4F2] text-[#00437A]" : "border-[#C6C6C6] bg-white text-[#333333]"
                }`}
              >
                {o}
              </button>
            ))}
          </div>
          <p className="mb-1 text-[11px] font-semibold uppercase tracking-wide text-[#4A5560]">3. Underlag som stöder bedömningen</p>
          <div className="mb-3 grid grid-cols-2 gap-1 sm:grid-cols-4">
            {aktivtRegelpaket().underlagskallor.map((k) => (
              <button
                key={k}
                onClick={() => vaxla(underlag, setUnderlag, k)}
                className={`min-h-8 border px-1.5 text-[12px] font-medium ${
                  underlag.includes(k) ? "border-[#00437A] bg-[#D6E4F2] text-[#00437A]" : "border-[#C6C6C6] bg-white text-[#333333]"
                }`}
              >
                {k}
              </button>
            ))}
          </div>
          <p className="mb-1 text-[11px] font-semibold uppercase tracking-wide text-[#4A5560]">4. Säkerhet i bedömningen</p>
          <div className="mb-3 grid grid-cols-3 gap-2">
            {(["hog", "medel", "lag"] as const).map((n) => (
              <button
                key={n}
                onClick={() => setSakerhet(n)}
                className={`min-h-9 border text-[12px] font-semibold ${
                  sakerhet === n ? "border-[#00437A] bg-[#00437A] text-white" : "border-[#ADADAD] bg-white text-[#333333]"
                }`}
              >
                {TILLFORLITLIGHET_LABEL[n]}
              </button>
            ))}
          </div>
          {sakerhet !== "hog" && (
            <TextFalt
              label="Vilka ytterligare kontroller skulle stärka bedömningen?"
              varde={ytterligare}
              satt={setYtterligare}
              rost
            />
          )}
          {orsaker.includes("Okänd orsak") && (
            <TextFalt
              label="Motivering — varför kunde orsaken inte fastställas?"
              varde={motivering}
              satt={setMotivering}
              flerRad
              rost
            />
          )}
          <TextFalt label="Rekommenderad åtgärd" varde={atgard} satt={setAtgard} rost platshallare="T.ex. Balansera framhjulen och genomför ny provkörning." />
          {fel && <p className="mb-2 text-[12px] font-semibold text-[#8B1A1A]">{fel}</p>}
          <div className="grid grid-cols-2 gap-2">
            <StorKnapp variant="sekundar" onClick={() => setOppen(false)}>
              Avbryt
            </StorKnapp>
            <StorKnapp onClick={spara}>Spara felorsak</StorKnapp>
          </div>
        </div>
      )}
    </Panel>
  );
}


// Åtgärdsfasen: reparationen dokumenteras (eller motiveras varför den
// uteblev) och kvalitetskontrollen sluter loopen som symptom-
// verifieringen öppnade — är symptomet faktiskt borta?
function AtgardsPanel({ arende, skicka }: { arende: Arende; skicka: (h: Handelse) => void }) {
  const poster = atgarder(arende);
  const utford = poster.some((p) => p.handelse.typ === "atgard_utford" && p.handelse.utford);
  const kk = kvalitetskontroll(arende);
  const repro = reproducering(arende);

  const forslag = atgardsforslag(arende);
  const beslut = kundbeslut(arende);
  const senasteFelorsak = felorsaker(arende).at(-1);
  const foreslagenAtgard =
    senasteFelorsak?.handelse.typ === "felorsak" ? senasteFelorsak.handelse.atgard : "";

  const [forslagsLage, setForslagsLage] = useState(false);
  const [forslagText, setForslagText] = useState("");
  const [kostnad, setKostnad] = useState("");
  const [beslutsVal, setBeslutsVal] = useState<"" | "godkant" | "avbojt" | "delvis">("");
  const [kanal, setKanal] = useState("");
  const [beslutsKommentar, setBeslutsKommentar] = useState("");

  const [lage, setLage] = useState<"" | "utford" | "ingen">("");
  const [beskrivning, setBeskrivning] = useState("");
  const [delar, setDelar] = useState("");
  const [orsak, setOrsak] = useState("");
  const [kkResultat, setKkResultat] = useState<"" | "symptomet_borta" | "kvarstar" | "delvis" | "ej_verifierbar">("");
  const [kkText, setKkText] = useState("");

  return (
    <Panel rubrik="Åtgärd och kvalitetskontroll">
      {poster.map((p) => {
        const h = p.handelse;
        if (h.typ !== "atgard_utford") return null;
        return (
          <p key={p.id} className="border-b border-[#EBEBEB] py-1 text-[13px] last:border-0">
            {h.utford ? (
              <>
                <span className="font-semibold">Utförd åtgärd:</span> {h.beskrivning}
                {h.delar && <span className="text-[#4A5560]"> · Delar: {h.delar}</span>}
              </>
            ) : (
              <span className="text-[#9A6700]">Ingen åtgärd utförd — {h.motivering}</span>
            )}
          </p>
        );
      })}
      {kk && (
        <p className={`py-1 text-[13px] font-semibold ${kk.resultat === "symptomet_borta" ? "text-[#1E6B34]" : "text-[#9A6700]"}`}>
          Kvalitetskontroll: {KVALITETSKONTROLL_LABEL[kk.resultat]} — <span className="font-normal">{kk.beskrivning}</span>
        </p>
      )}

      {/* Kundkommunikation: förslaget lämnas för godkännande innan
          arbetet påbörjas, och beskedet registreras med kanal. */}
      {forslag.map((p) => {
        const h = p.handelse;
        if (h.typ !== "atgardsforslag") return null;
        return (
          <p key={p.id} className="border-b border-[#EBEBEB] py-1 text-[13px] last:border-0">
            <span className="font-semibold">Åtgärdsförslag till kund:</span> {h.beskrivning}
            {h.uppskattadKostnad && <span className="text-[#4A5560]"> · Uppskattad kostnad: {h.uppskattadKostnad}</span>}
          </p>
        );
      })}
      {beslut && (
        <p className={`py-1 text-[13px] font-semibold ${beslut.beslut === "godkant" ? "text-[#1E6B34]" : beslut.beslut === "avbojt" ? "text-[#8B1A1A]" : "text-[#9A6700]"}`}>
          Kundens besked ({beslut.kanal}): {KUNDBESLUT_LABEL[beslut.beslut]}
          {beslut.kommentar && <span className="font-normal"> — {beslut.kommentar}</span>}
        </p>
      )}

      {/* Förslaget hör före arbetet, och knappen står därför först. Men
          den får inte försvinna när arbetet väl dokumenterats: grinden
          kräver kundens besked för utfört arbete, och utan den här vägen
          blir ett ärende där teknikern skrev åtgärden först omöjligt att
          stänga — utan att någonstans säga varför. Loggen är append-only
          med tidsstämplar, så ett besked som registreras i efterhand
          syns som just det. */}
      {forslag.length === 0 && !forslagsLage && (
        <StorKnapp
          variant="sekundar"
          className="mb-2"
          onClick={() => {
            setForslagsLage(true);
            setForslagText(foreslagenAtgard);
          }}
        >
          Lämna åtgärdsförslag till kund
        </StorKnapp>
      )}

      {forslagsLage && (
        <div className="mb-3 border border-[#C6C6C6] bg-white p-2">
          <TextFalt
            label="Föreslagen åtgärd (visas för kunden i delningsvyn)"
            varde={forslagText}
            satt={setForslagText}
            flerRad
            rost
            platshallare="T.ex. Balansera om höger framhjul och genomför ny provkörning."
          />
          <TextFalt label="Uppskattad kostnad (valfritt)" varde={kostnad} satt={setKostnad} platshallare="T.ex. 1 200 kr inkl. moms" />
          <div className="grid grid-cols-2 gap-2">
            <StorKnapp variant="sekundar" onClick={() => setForslagsLage(false)}>
              Avbryt
            </StorKnapp>
            <StorKnapp
              disabled={forslagText.trim().length < 10}
              onClick={() => {
                skicka({
                  typ: "atgardsforslag",
                  beskrivning: forslagText.trim(),
                  uppskattadKostnad: kostnad.trim() || undefined,
                });
                setForslagsLage(false);
                setForslagText("");
                setKostnad("");
              }}
            >
              Lämna förslag
            </StorKnapp>
          </div>
        </div>
      )}

      {forslag.length > 0 && !beslut && (
        <div className="mb-3 border border-[#E0C36A] bg-[#FFF8E1] p-2">
          <p className="mb-2 text-[12px] font-semibold uppercase tracking-wide text-[#9A6700]">
            Registrera kundens besked innan arbetet påbörjas
          </p>
          <div className="mb-2 grid grid-cols-3 gap-2">
            {(["godkant", "avbojt", "delvis"] as const).map((val) => (
              <button
                key={val}
                onClick={() => setBeslutsVal(val)}
                className={`min-h-9 border text-[12px] font-semibold ${
                  beslutsVal === val ? "border-[#00437A] bg-[#00437A] text-white" : "border-[#ADADAD] bg-white text-[#333333]"
                }`}
              >
                {KUNDBESLUT_LABEL[val]}
              </button>
            ))}
          </div>
          {beslutsVal && (
            <>
              <p className="mb-1 text-[11px] font-semibold uppercase tracking-wide text-[#4A5560]">Hur lämnades beskedet?</p>
              <div className="mb-2 grid grid-cols-3 gap-1">
                {KUNDKANALER.map((k) => (
                  <button
                    key={k}
                    onClick={() => setKanal(k)}
                    className={`min-h-8 border px-1 text-[12px] font-medium ${
                      kanal === k ? "border-[#00437A] bg-[#D6E4F2] text-[#00437A]" : "border-[#C6C6C6] bg-white text-[#333333]"
                    }`}
                  >
                    {k}
                  </button>
                ))}
              </div>
              <TextFalt
                label={beslutsVal === "godkant" ? "Kommentar (valfritt)" : "Kundens motivering (obligatorisk)"}
                varde={beslutsKommentar}
                satt={setBeslutsKommentar}
                rost
              />
              <StorKnapp
                disabled={!kanal || (beslutsVal !== "godkant" && !beslutsKommentar.trim())}
                onClick={() => {
                  skicka({
                    typ: "kundbeslut",
                    beslut: beslutsVal,
                    kanal,
                    kommentar: beslutsKommentar.trim() || undefined,
                  });
                  setBeslutsVal("");
                  setKanal("");
                  setBeslutsKommentar("");
                }}
              >
                Registrera besked
              </StorKnapp>
            </>
          )}
        </div>
      )}

      {poster.length === 0 && !lage && (
        <div className="grid grid-cols-2 gap-2">
          <StorKnapp
            variant="sekundar"
            disabled={forslag.length > 0 && (!beslut || beslut.beslut === "avbojt")}
            onClick={() => setLage("utford")}
          >
            Dokumentera utförd åtgärd
          </StorKnapp>
          <StorKnapp variant="sekundar" onClick={() => setLage("ingen")}>
            Ingen åtgärd utförd
          </StorKnapp>
        </div>
      )}

      {lage === "utford" && (
        <div className="mt-2">
          <TextFalt
            label="Vad gjordes? (kopplat till den dokumenterade felorsaken)"
            varde={beskrivning}
            satt={setBeskrivning}
            flerRad
            rost
            platshallare="T.ex. Balanserade om höger framhjul, ny balansvikt 40 g på insidan."
          />
          <TextFalt label="Delar (valfritt)" varde={delar} satt={setDelar} platshallare="T.ex. Balansvikter, artikelnr 30-1234" rost />
          <div className="grid grid-cols-2 gap-2">
            <StorKnapp variant="sekundar" onClick={() => setLage("")}>
              Avbryt
            </StorKnapp>
            <StorKnapp
              disabled={beskrivning.trim().length < 10}
              onClick={() => {
                skicka({ typ: "atgard_utford", beskrivning: beskrivning.trim(), delar: delar.trim() || undefined, utford: true });
                setLage("");
                setBeskrivning("");
                setDelar("");
              }}
            >
              Spara åtgärd
            </StorKnapp>
          </div>
        </div>
      )}

      {lage === "ingen" && (
        <div className="mt-2">
          <p className="mb-2 text-[12px] font-semibold uppercase tracking-wide text-[#4A5560]">
            Orsak till att ingen åtgärd utfördes (obligatorisk)
          </p>
          <div className="mb-2 grid gap-1">
            {INGEN_ATGARD_ORSAKER.map((val) => (
              <button
                key={val}
                onClick={() => setOrsak(val)}
                className={`min-h-8 border px-2 text-left text-[12px] font-medium ${
                  orsak === val ? "border-[#00437A] bg-[#D6E4F2]" : "border-[#C6C6C6] bg-white"
                }`}
              >
                {val}
              </button>
            ))}
          </div>
          <TextFalt label="Eller egen orsak" varde={orsak && !INGEN_ATGARD_ORSAKER.includes(orsak) ? orsak : ""} satt={setOrsak} rost />
          {beslut?.beslut === "avbojt" && (
            <p className="mb-2 text-[11px] text-[#707070]">
              Kunden har avböjt åtgärden via {beslut.kanal} — beskedet finns registrerat i loggen.
            </p>
          )}
          <div className="grid grid-cols-2 gap-2">
            <StorKnapp variant="sekundar" onClick={() => setLage("")}>
              Avbryt
            </StorKnapp>
            <StorKnapp
              disabled={!orsak.trim()}
              onClick={() => {
                skicka({ typ: "atgard_utford", beskrivning: "Ingen åtgärd utförd", utford: false, motivering: orsak.trim() });
                setLage("");
                setOrsak("");
              }}
            >
              Dokumentera
            </StorKnapp>
          </div>
        </div>
      )}

      {utford && !kk && (
        <div className="mt-3 border-t border-[#DDDDDD] pt-2">
          <p className="mb-2 text-[12px] font-semibold uppercase tracking-wide text-[#4A5560]">
            Kvalitetskontroll — är symptomet borta efter åtgärden?
          </p>
          {repro?.status === "ja" && (
            <p className="mb-2 text-[12px] text-[#707070]">
              Symptomet reproducerades vid undersökningen — återskapa samma förhållanden för att verifiera.
            </p>
          )}
          <div className="mb-2 grid grid-cols-2 gap-2">
            {(["symptomet_borta", "kvarstar", "delvis", "ej_verifierbar"] as const).map((val) => (
              <button
                key={val}
                onClick={() => setKkResultat(val)}
                className={`min-h-9 border px-2 text-[12px] font-semibold ${
                  kkResultat === val ? "border-[#00437A] bg-[#00437A] text-white" : "border-[#ADADAD] bg-white text-[#333333]"
                }`}
              >
                {KVALITETSKONTROLL_LABEL[val]}
              </button>
            ))}
          </div>
          {kkResultat && (
            <>
              <TextFalt
                label={
                  kkResultat === "symptomet_borta"
                    ? "Hur verifierades det? (förhållanden, hastighet, provkörning …)"
                    : kkResultat === "ej_verifierbar"
                      ? "Varför kunde det inte verifieras? (obligatorisk)"
                      : "Vad kvarstår? (obligatorisk)"
                }
                varde={kkText}
                satt={setKkText}
                flerRad
                rost
                platshallare="T.ex. Ny provkörning 80–110 km/h på samma vägsträcka — ingen vibration."
              />
              <StorKnapp
                disabled={!kkText.trim()}
                onClick={() => {
                  skicka({ typ: "kvalitetskontroll", resultat: kkResultat, beskrivning: kkText.trim() });
                  setKkResultat("");
                  setKkText("");
                }}
              >
                Spara kvalitetskontroll
              </StorKnapp>
            </>
          )}
        </div>
      )}
    </Panel>
  );
}

// Navigationsträd (vänsterkolumnen): ärendets vyer plus metodikens steg
// med status — som en mappstruktur i ett klassiskt verkstadssystem.
function VyTrad({
  flik,
  sattFlik,
  arende,
  metodik,
}: {
  flik: Flik;
  sattFlik: (f: Flik) => void;
  arende: Arende;
  metodik: Metodik;
}) {
  const steg = nastaSteg(arende, metodik);
  const aktuellIndex = metodik.steg.findIndex((s) => s.id === steg.steg.id);
  return (
    <nav className="overflow-hidden border border-[#C6C6C6] bg-[#F7F7F7] shadow-[0_1px_2px_rgba(0,0,0,0.06)]">
      <p className="border-b border-[#DDDDDD] bg-[#EFEFEF] px-2.5 py-1.5 text-[11px] font-semibold uppercase tracking-wider text-[#4A5560]">
        Ärendevyer
      </p>
      {FLIKAR.map((f) => (
        <button
          key={f.id}
          onClick={() => sattFlik(f.id)}
          className={`block w-full border-l-2 px-3 py-1.5 text-left text-[13px] transition-colors ${
            flik === f.id
              ? "border-[#00437A] bg-[#D6E4F2] font-semibold text-[#00437A]"
              : "border-transparent text-[#333333] hover:bg-[#E4E9EE]"
          }`}
        >
          {f.label}
        </button>
      ))}
      <p className="border-y border-[#DDDDDD] bg-[#EFEFEF] px-2.5 py-1.5 text-[11px] font-semibold uppercase tracking-wider text-[#4A5560]">
        {metodik.namn}
      </p>
      {metodik.steg.map((s, i) => {
        const klar = steg.klart || i < aktuellIndex;
        const aktuell = !steg.klart && i === aktuellIndex;
        return (
          <p
            key={s.id}
            className={`px-3 py-1 text-[12px] ${
              aktuell ? "bg-[#FFF8E1] font-semibold text-[#9A6700]" : klar ? "text-[#1E6B34]" : "text-[#707070]"
            }`}
          >
            {klar ? "✓" : aktuell ? "▸" : "·"} {s.rubrik}
          </p>
        );
      })}
    </nav>
  );
}

// Kontextpanel (högerkolumnen): teknisk information, tillförlitlighet och
// senaste tekniska rekommendation — alltid synlig på breda skärmar.
function KontextPanel({
  arende,
  metodik,
  nu,
  synkStatus,
}: {
  arende: Arende;
  metodik: Metodik;
  nu: string;
  synkStatus: SynkStatus;
}) {
  const b = brief(arende, metodik, nu);
  const senasteAi = [...arende.handelser].reverse().find((p) => p.handelse.typ === "ai_svar");
  const rad = (etikett: string, varde: string | undefined) =>
    varde ? (
      <div className="flex justify-between gap-2 border-b border-[#EBEBEB] py-1 text-[12px] last:border-0">
        <span className="shrink-0 text-[#4A5560]">{etikett}</span>
        <span className="text-right font-medium text-[#1A1A1A]">{varde}</span>
      </div>
    ) : null;
  return (
    <div>
      <Panel rubrik="Teknisk information">
        {rad("Objekt", b.objekt?.beskrivning)}
        {rad("Ident", b.objekt?.identifierare)}
        {rad("Kund", b.objekt?.kund)}
        {rad("Ansvarig", b.ansvarig)}
        {rad("Status", b.avslutat ? "Avslutat" : "Pågår")}
        {rad("Synk", SYNKSTATUS_LABEL[synkStatus])}
        {rad("Arbetstid", b.totalArbetstid)}
      </Panel>
      <Panel rubrik="Tillförlitlighet">
        {b.tillforlitlighet.map((r, i) => (
          <p key={i} className="py-0.5 text-[12px]">
            <NivaBadge niva={r.niva} /> {r.text}
          </p>
        ))}
      </Panel>
      {!b.avslutat && b.rekommenderatNastaSteg.length > 0 && (
        <Panel rubrik="Rekommenderat nästa steg">
          {b.rekommenderatNastaSteg.map((s, i) => (
            <p key={i} className="py-0.5 text-[12px] text-[#333333]">
              {i + 1}. {s}
            </p>
          ))}
        </Panel>
      )}
      {senasteAi && senasteAi.handelse.typ === "ai_svar" && (
        <Panel rubrik="Teknisk rekommendation">
          {senasteAi.handelse.rader.map((r, i) => (
            <p key={i} className="py-0.5 text-[12px]">
              <span className="font-semibold text-[#4A5560]">{AI_RADTYP_LABEL[r.typ]}:</span> {r.text}
            </p>
          ))}
          <p className="mt-1 text-[12px]">
            <span className="font-semibold text-[#00437A]">Nästa steg:</span> {senasteAi.handelse.nastaSteg}
          </p>
          <p className="mt-1 text-[10px] uppercase tracking-wide text-[#707070]">
            Beslutsstöd · presenteras aldrig som konstaterat fel
          </p>
        </Panel>
      )}
    </div>
  );
}

// Vid längre inaktivitet ber systemet om sammanhang — svaret blir en loggpost.
function InaktivitetsBanner({
  arende,
  nu,
  skicka,
}: {
  arende: Arende;
  nu: string;
  skicka: (h: Handelse) => void;
}) {
  const [text, setText] = useState("");
  const sista = sistaAktivitet(arende);
  if (!sista) return null;
  const minuter = Math.floor((new Date(nu).getTime() - new Date(sista.tidpunkt).getTime()) / 60000);
  if (minuter < 20) return null;
  return (
    <div className="mb-4 border border-[#00437A] bg-[#F7F7F7] p-4">
      <p className="mb-2 text-[14px] font-semibold text-[#00437A]">
        Ingen aktivitet har registrerats de senaste {minuter} minuterna.
      </p>
      <p className="mb-3 text-[#333333]">Beskriv kort vad som gjorts under denna period.</p>
      <TextFalt label="Vad har gjorts?" varde={text} satt={setText} platshallare="T.ex. Demonterade instrumentpanelen för att komma åt kabelstammen." rost />
      <StorKnapp
        disabled={!text.trim()}
        onClick={() => {
          skicka({ typ: "inaktivitet_forklarad", text: text.trim(), minuter });
          setText("");
        }}
      >
        Lägg till i arbetsloggen
      </StorKnapp>
    </div>
  );
}

function GuideFlik({
  arende,
  metodik,
  avslutat,
  skicka,
  nu,
}: {
  arende: Arende;
  metodik: Metodik;
  avslutat: boolean;
  skicka: (h: Handelse) => void;
  nu: string;
}) {
  const steg = useMemo(() => nastaSteg(arende, metodik), [arende, metodik]);
  const anvandare = useFelsokning((s) => s.anvandare);
  const [visaOverlamning, setVisaOverlamning] = useState(false);
  const [aiStatus, setAiStatus] = useState<"vilar" | "arbetar" | "fel">("vilar");

  // AI-orkestern drivs av plattformen: handledningen (Sonnet 5) svarar på
  // varje bekräftad dokumentation, klassificerat och loggat som händelse.
  // I lokalt läge (ej inloggad) guidar den deterministiska metodiken ensam.
  const fragaAiOmDokumentation = async (inmatning: string) => {
    setAiStatus("arbetar");
    try {
      const svar = await fragaAi(brief(arende, metodik, nu), metodik.namn, inmatning);
      if (svar) {
        skicka({ typ: "ai_svar", rader: svar.rader, nastaSteg: svar.nastaSteg, modell: svar.modell });
      }
      setAiStatus("vilar");
    } catch {
      setAiStatus("fel");
    }
  };

  const senasteAiSvar = [...arende.handelser].reverse().find((p) => p.handelse.typ === "ai_svar");

  // QUALITY-AUDIT-2 · M-7. Klienten upprepar inte längre grindens regel.
  //
  // Den gjorde det tidigare, för att kunna gråa ut avslutsknappen, och
  // villkoret gled isär från grinden två gånger: först saknades kravet på
  // en slutsats, sedan kravet på kundens besked. Ingen av gångerna
  // fångades av testerna — bägge hittades av att köra tio ärenden hela
  // vägen. Två självständiga uttryck för en regel glider alltid; frågan
  // är bara när det upptäcks.
  //
  // Nu ställs frågan till grinden själv, med den lokala loggen. Samma
  // funktion som servern kör, så svaret kan inte skilja sig. Att den
  // därmed blir strängare är avsikten: det servern skulle neka ska synas
  // på skärmen där arbetet utförs, inte vid synk när bilen har åkt.
  const hinder = grinda(
    arende.handelser.map((p) => p.handelse),
    metodik,
  ) as { id: string; rubrik: string; detalj?: string }[];

  const kanAvslutas = hinder.length === 0;

  // Slutsatspanelen visas när underlaget är helt — alltså när det enda
  // som återstår är varför-frågan. Att fråga tidigare vore att be om en
  // gissning; att fråga först vid knappen vore att neka någon som redan
  // är klar.
  const underlagKlart = hinder.every((h) => h.id.startsWith("slutsats_"));

  if (avslutat) {
    return (
      <Panel rubrik="Felsökningen är avslutad">
        <p className="text-[14px] text-[#333333]">
          Ärendet är låst för nya guidesteg. Loggen, briefen och rapporten finns kvar för spårbarhet och export.
        </p>
      </Panel>
    );
  }

  // Pre-Diagnostic Validation: metodiken öppnas först när grund-
  // kontrollerna är genomförda eller dokumenterat motiverade.
  if (!preDiagnostik(arende).every((r) => r.klar)) {
    return (
      <>
        <KategoriRad arende={arende} skicka={skicka} />
        <PreDiagnostikPanel arende={arende} skicka={skicka} />
        <SnabbDokumentation skicka={skicka} paSparad={fragaAiOmDokumentation} />
      </>
    );
  }

  return (
    <>
      <KategoriRad arende={arende} skicka={skicka} />

      {/* ALVA-modellen som tillstånd, inte som illustration. Den svarar
          på den enda fråga en tekniker som återupptar ett ärende har:
          var i metoden är vi? Fasen härleds ur steget, så indikatorn kan
          aldrig visa något annat än vad metodiken faktiskt gör. */}
      <div className="mb-4">
        <div className="mb-2 flex items-baseline justify-between">
          <span className="text-[11px] font-semibold uppercase tracking-[0.12em] text-[#4D5662]">
            {fasDefinition(fasFor(steg.steg.id)).namn} — {fasDefinition(fasFor(steg.steg.id)).syfte}
          </span>
          <span className="font-mono text-[11px] text-[#4D5662]">{arendebeteckning(arende.nummer)}</span>
        </div>
        <Fasrad
          aktiv={fasFor(steg.steg.id)}
          klara={klaraFaser(metodik, arende.handelser.map((p) => p.handelse))}
        />
        <p className="mt-2 text-[11px] leading-[16px] text-[#4D5662]">
          {fasDefinition(fasFor(steg.steg.id)).avgransning}
        </p>
      </div>

      {/* ALVA-RULE-200. Panelen visas när det finns ett underlag att
          vila på — antingen för att metodiken är genomförd eller för att
          kedjan reproducering → felorsak → åtgärd → kvalitetskontroll är
          hel. Att fråga tidigare vore att be om en gissning; att fråga
          först vid avslutsknappen vore att neka någon som redan är klar. */}
      {(steg.klart || underlagKlart) && <Slutsatspanel arende={arende} skicka={skicka} />}

      <Panel rubrik={`Metodik: ${metodik.namn} · Steg: ${steg.steg.rubrik}`}>
        {steg.klart ? (
          <>
            <p className="mb-3 text-[15px] font-semibold">Samtliga steg i metodiken är dokumenterade.</p>
            <p className="mb-4 text-[#333333]">
              Om felorsaken inte är verifierad: dokumentera en hypotes och utöka felsökningen, eller avsluta ärendet
              med rekommenderade nästa steg.
            </p>
            <StorKnapp variant="fara" disabled={!kanAvslutas} onClick={() => skicka({ typ: "arende_avslutat", signatur: anvandare, plattformsversion: PLATTFORMSVERSION })}>
              Avsluta felsökning
            </StorKnapp>
            {!kanAvslutas && <Avslutshinder hinder={hinder} />}
          </>
        ) : steg.sparr ? (
          // Säkerhetsspärr. Metodiken går inte vidare — svaret är ett
          // hinder, inte en varning. Enda vägen framåt är att ändra
          // förutsättningen, inte att klicka förbi.
          <div className="border-2 border-[#B42318] bg-[#FEF3F2] p-4">
            <p className="mb-2 text-[15px] font-semibold text-[#B42318]">Arbetet får inte fortsätta</p>
            <p className="mb-2 text-[#333333]">{steg.sparr.orsak}</p>
            <p className="mb-3 font-semibold text-[#333333]">{steg.sparr.atgard}</p>
            <p className="text-[12px] text-[#666666]">
              Svaret är dokumenterat i loggen. Metodiken öppnas när förutsättningen är uppfylld och frågan besvaras
              jakande.
            </p>
          </div>
        ) : steg.fraga ? (
          <FrageKort key={`${steg.steg.id}/${steg.fraga.id}`} steg={steg} skicka={skicka} />
        ) : steg.kontroll ? (
          <KontrollKort key={`${steg.steg.id}/${steg.kontroll.id}`} steg={steg} skicka={skicka} />
        ) : null}
      </Panel>

      {(aiStatus !== "vilar" || senasteAiSvar) && (
        <Panel
          rubrik={
            senasteAiSvar?.handelse.typ === "ai_svar"
              ? `Handledning (${senasteAiSvar.handelse.modell})`
              : "Handledning"
          }
        >
          {aiStatus === "arbetar" && <p className="mb-2 animate-pulse font-semibold text-[#00437A]">Systemet analyserar …</p>}
          {aiStatus === "fel" && (
            <p className="mb-2 font-semibold text-[#8B1A1A]">Analysen kunde inte hämtas — försök igen. Metodiken fortsätter som vanligt.</p>
          )}
          {senasteAiSvar && senasteAiSvar.handelse.typ === "ai_svar" && (
            <>
              {senasteAiSvar.handelse.rader.map((rad, i) => (
                <p key={i} className="py-0.5 text-[14px]">
                  <span className="font-semibold text-[#4A5560]">{AI_RADTYP_LABEL[rad.typ]}:</span> {rad.text}
                </p>
              ))}
              <p className="mt-2 text-[14px]">
                <span className="font-semibold text-[#00437A]">Nästa steg:</span>{" "}
                {senasteAiSvar.handelse.nastaSteg}
              </p>
            </>
          )}
        </Panel>
      )}

      <SnabbDokumentation skicka={skicka} paSparad={fragaAiOmDokumentation} />

      {/* SVP: symptomet ska reproduceras — eller dokumenteras som ej
          reproducerbart med motivering — innan ärendet kan avslutas. */}
      {!reproducering(arende) && <ReproduceringPanel skicka={skicka} />}

      {/* Felorsaksanalys: aldrig bara "komponent trasig, byt komponent" —
          varje konstaterat fel kräver avvikelse, orsak, underlag och
          säkerhetsnivå. Obligatorisk för avslut. */}
      <FelorsaksPanel arende={arende} skicka={skicka} />

      {/* Åtgärdsfasen: vad som gjordes — och kvalitetskontrollen som
          verifierar att symptomet faktiskt är borta. */}
      {felorsaker(arende).length > 0 && <AtgardsPanel arende={arende} skicka={skicka} />}

      {/* Utgående mätarställning: obligatorisk för kvalitetsgrinden när
          ärendet avslutas — erbjuds så fort metodiken är genomarbetad. */}
      {!arende.handelser.some((p) => p.handelse.typ === "matarstallning" && p.handelse.lage === "utgaende") && (
        <Panel rubrik="Utgående mätarställning — inför avslut">
          <MatarstallningSteg lage="utgaende" skicka={skicka} />
        </Panel>
      )}

      {!kanAvslutas && <Avslutshinder hinder={hinder} />}
      <div className="grid grid-cols-2 gap-2">
        <StorKnapp variant="sekundar" onClick={() => setVisaOverlamning(true)}>
          Lämna över arbete
        </StorKnapp>
        <StorKnapp variant="sekundar" disabled={!kanAvslutas} onClick={() => skicka({ typ: "arende_avslutat", signatur: anvandare, plattformsversion: PLATTFORMSVERSION })}>
          Avsluta felsökning
        </StorKnapp>
      </div>

      {visaOverlamning && (
        <OverlamningDialog arende={arende} metodik={metodik} nu={nu} skicka={skicka} stang={() => setVisaOverlamning(false)} />
      )}
    </>
  );
}

/**
 * Varför avslut är spärrat — i klartext, på skärmen, medan arbetet pågår.
 *
 * Listan är grindens egen. Det är hela poängen: teknikern läser exakt de
 * hinder som servern skulle svara med, vid den tidpunkt då de fortfarande
 * går att åtgärda. Ett hinder som bara säger "kraven är inte uppfyllda"
 * tvingar teknikern att leta.
 */
function Avslutshinder({ hinder }: { hinder: { id: string; rubrik: string; detalj?: string }[] }) {
  return (
    <div className="mb-2 border-l-2 border-[#9A6700] bg-[#FFFBF0] px-4 py-4">
      <p className="mb-2 text-[12px] font-semibold uppercase tracking-[0.08em] text-[#9A6700]">
        Ärendet kan inte avslutas ännu
      </p>
      <ul className="ml-4 list-disc text-[13px] leading-[20px] text-[#333333]">
        {hinder.map((h) => (
          <li key={h.id}>
            {h.rubrik}
            {h.detalj && <span className="text-[#4A5560]"> — {h.detalj}</span>}
          </li>
        ))}
      </ul>
    </div>
  );
}

function KategoriRad({ arende, skicka }: { arende: Arende; skicka: (h: Handelse) => void }) {
  let aktiv: TidKategori = "aktiv_felsokning";
  for (const post of arende.handelser) {
    if (post.handelse.typ === "kategori_byte") aktiv = post.handelse.kategori;
  }
  return (
    <div className="mb-4 flex gap-2 overflow-x-auto pb-1">
      {(Object.keys(TIDKATEGORI_LABEL) as TidKategori[]).map((k) => (
        <button
          key={k}
          onClick={() => k !== aktiv && skicka({ typ: "kategori_byte", kategori: k })}
          className={`whitespace-nowrap border px-4 py-2 text-[12px] font-semibold transition-colors ${
            k === aktiv
              ? "border-[#00437A] bg-[#00437A] text-white"
              : "border-[#ADADAD] bg-[#F7F7F7] text-[#333333] hover:border-[#8FA8C0]"
          }`}
        >
          {TIDKATEGORI_LABEL[k]}
        </button>
      ))}
    </div>
  );
}

// En fråga i taget, stora svarsknappar.
function FrageKort({ steg, skicka }: { steg: NastaSteg; skicka: (h: Handelse) => void }) {
  const fraga = steg.fraga!;
  const [text, setText] = useState("");
  const svara = (svar: string) =>
    skicka({ typ: "fraga_besvarad", stegId: steg.steg.id, frageId: fraga.id, fraga: fraga.text, svar });

  return (
    <>
      <p className="mb-4 text-[17px] font-semibold leading-snug">{fraga.text}</p>
      {fraga.svarstyp === "janej" && (
        <div className="grid grid-cols-2 gap-2">
          <StorKnapp onClick={() => svara("Ja")}>Ja</StorKnapp>
          <StorKnapp onClick={() => svara("Nej")}>Nej</StorKnapp>
        </div>
      )}
      {fraga.svarstyp === "val" && (
        <div className="grid gap-2">
          {fraga.val!.map((v) => (
            <StorKnapp key={v} variant="sekundar" onClick={() => svara(v)}>
              {v}
            </StorKnapp>
          ))}
        </div>
      )}
      {fraga.svarstyp === "text" && (
        <>
          <TextFalt label="Svar" varde={text} satt={setText} rost />
          <StorKnapp disabled={!text.trim()} onClick={() => svara(text.trim())}>
            Spara svar
          </StorKnapp>
        </>
      )}
    </>
  );
}

// Verifierad checklista: en kontrollpunkt är inte slutförd enbart genom en
// kryssruta — minimikravet (foto, mätvärde eller kort observation) styrs av
// kontrolltypen i metodiken.
function KontrollKort({ steg, skicka }: { steg: NastaSteg; skicka: (h: Handelse) => void }) {
  const kontroll = steg.kontroll!;
  const [resultat, setResultat] = useState("");
  const filRef = useRef<HTMLInputElement>(null);
  const krav = kontroll.krav;

  const utford = () => {
    // En kontroll vars krav är ett mätvärde ger ett mätvärde. Tidigare
    // loggades bara kontroll_utford, med följden att 56 av metodikernas
    // 153 kontroller — drygt en tredjedel — mätte utan att det syntes:
    // felorsaksanalysen nekade ”Mätresultat” som underlag, och
    // evidensprofilen i analysvyn underskattade systematiskt hur mycket
    // som faktiskt mättes. Mätningen loggas därför som det den är.
    //
    // Utan känt mätdon graderas den E1, inte E4 (QUALITY-AUDIT M-1) —
    // ett avläst tal utan spårbart instrument är inte en spårbar mätning.
    if (krav === "matvarde" && resultat.trim()) {
      skicka({ typ: "matvarde", beskrivning: kontroll.text, varde: resultat.trim() });
    }
    skicka({
      typ: "kontroll_utford",
      stegId: steg.steg.id,
      kontrollId: kontroll.id,
      text: kontroll.text,
      resultat: resultat.trim() || undefined,
    });
  };

  const kravUppfyllt = krav === "foto" || !krav || resultat.trim().length > 0;

  return (
    <>
      {steg.steg.beskrivning && <p className="mb-2 text-[#4A5560]">{steg.steg.beskrivning}</p>}
      <p className="mb-4 text-[17px] font-semibold leading-snug">{kontroll.text}</p>
      {krav === "foto" ? (
        <>
          <input
            ref={filRef}
            type="file"
            accept="image/*"
            capture="environment"
            className="hidden"
            onChange={async (e) => {
              const fil = e.target.files?.[0];
              if (!fil) return;
              const dataUrl = await skalaNerFoto(fil);
              skicka({ typ: "foto", beskrivning: kontroll.text, dataUrl });
              utford();
            }}
          />
          <TextFalt label="Observation (valfritt)" varde={resultat} satt={setResultat} rost />
          <StorKnapp onClick={() => filRef.current?.click()}><IkonKamera /> Ta foto — verifierar kontrollen</StorKnapp>
          <p className="mt-2 text-[12px] text-[#707070]">Denna kontroll verifieras med foto.</p>
        </>
      ) : (
        <>
          <TextFalt
            label={
              krav === "matvarde"
                ? "Uppmätt värde (krävs för verifiering)"
                : krav === "kommentar"
                  ? "Vad observerades? (krävs för verifiering)"
                  : "Resultat / avläst värde (valfritt)"
            }
            varde={resultat}
            satt={setResultat}
            platshallare={krav === "matvarde" ? "T.ex. 2,4 bar samtliga hjul" : "T.ex. Säkringen är hel, spänning på båda sidor"}
            rost
          />
          {!kravUppfyllt && (
            <p className="mb-3 text-[12px] font-semibold text-[#00437A]">
              Ingen {krav === "matvarde" ? "mätning" : "observation"} har registrerats — lägg till en kort{" "}
              {krav === "matvarde" ? "mätuppgift" : "kommentar"} innan kontrollen kan verifieras.
            </p>
          )}
          <StorKnapp disabled={!kravUppfyllt} onClick={utford}>
            Markera verifierad
          </StorKnapp>
        </>
      )}
      <Undantag
        vidUndantag={(orsak) =>
          skicka({
            typ: "kontroll_utford",
            stegId: steg.steg.id,
            kontrollId: kontroll.id,
            text: kontroll.text,
            undantag: orsak,
          })
        }
      />
    </>
  );
}

// ECM-regeln: en kontroll kan bara slutföras med evidens ELLER ett
// uttryckligt undantag — "underlaget kan inte tas fram" — där orsaken
// alltid är obligatorisk och loggas i händelseloggen.
function Undantag({ vidUndantag }: { vidUndantag: (orsak: string) => void }) {
  const [oppen, setOppen] = useState(false);
  const [orsak, setOrsak] = useState("");

  if (!oppen) {
    return (
      <button
        onClick={() => setOppen(true)}
        className="mt-2 w-full py-1 text-[12px] font-medium text-[#707070] underline-offset-2 hover:text-[#8B1A1A] hover:underline print:hidden"
      >
        Underlag kan inte tas fram …
      </button>
    );
  }
  return (
    <div className="mt-3 border border-[#E0C36A] bg-[#FFF8E1] p-2">
      <p className="mb-2 text-[12px] font-semibold text-[#9A6700]">
        Kontrollen dokumenteras utan underlag — ange orsak (obligatoriskt). Detta flaggas i brief och rapport.
      </p>
      <div className="mb-2 grid grid-cols-1 gap-1">
        {aktivtRegelpaket().undantagsorsaker.map((val) => (
          <button
            key={val}
            onClick={() => setOrsak(val)}
            className={`min-h-8 border px-2 text-left text-[12px] font-medium ${
              orsak === val ? "border-[#00437A] bg-[#D6E4F2]" : "border-[#C6C6C6] bg-white"
            }`}
          >
            {val}
          </button>
        ))}
      </div>
      <TextFalt label="Eller egen orsak" varde={orsak && !UNDANTAGSORSAKER.includes(orsak) ? orsak : ""} satt={setOrsak} rost />
      <div className="grid grid-cols-2 gap-2">
        <StorKnapp variant="sekundar" onClick={() => setOppen(false)}>
          Avbryt
        </StorKnapp>
        <StorKnapp variant="fara" disabled={!orsak.trim()} onClick={() => vidUndantag(orsak.trim())}>
          Dokumentera undantag
        </StorKnapp>
      </div>
    </div>
  );
}

const DOKTYPER = [
  { id: "observation", label: "Observation" },
  { id: "matvarde", label: "Mätvärde" },
  { id: "foto", label: "Foto" },
  { id: "video", label: "Video" },
  { id: "instrument", label: "Instrument" },
  { id: "hypotes", label: "Hypotes" },
  { id: "kommentar", label: "Kommentar" },
] as const;

// Fri dokumentation vid sidan av guiden. Hypoteser märks alltid som
// ej verifierade — de kan aldrig loggas som konstaterade fel.
// paSparad anropas med en textsammanfattning EFTER att användaren bekräftat
// med Spara — det är den texten AI:n får (aldrig obekräftad inmatning).
function SnabbDokumentation({
  skicka,
  paSparad,
}: {
  skicka: (h: Handelse) => void;
  paSparad?: (sammanfattning: string) => void;
}) {
  const [typ, setTyp] = useState<(typeof DOKTYPER)[number]["id"] | null>(null);
  const [text, setText] = useState("");
  const [varde, setVarde] = useState("");
  const filRef = useRef<HTMLInputElement>(null);
  const instRef = useRef<HTMLInputElement>(null);
  const videoRef = useRef<HTMLInputElement>(null);
  const [video, setVideo] = useState<{ dataUrl: string; beskrivning: string } | null>(null);
  const [videoFel, setVideoFel] = useState("");
  const [avlasning, setAvlasning] = useState<{ foto: string; tolkning: InstrumentTolkning; demo?: boolean } | null>(null);
  const [laserAv, setLaserAv] = useState(false);

  const aterstall = () => {
    setTyp(null);
    setText("");
    setVarde("");
  };

  // Visual-first: instrumentet/skärmen fotograferas, bildtolkningen
  // extraherar värdena och teknikern bekräftar innan något loggas.
  // Originalbilden bevaras alltid som evidens bredvid de strukturerade
  // värdena. I lokalt läge visas en tydligt märkt demo-avläsning.
  const lasAv = async (fil: File) => {
    setLaserAv(true);
    const foto = await skalaNerFoto(fil);
    try {
      const resultat = await lasAvInstrument(foto);
      if (resultat && resultat.tolkning.varden.length > 0) {
        setAvlasning({ foto, tolkning: resultat.tolkning });
      } else if (!resultat) {
        setAvlasning({ foto, tolkning: byggDemoInstrument(), demo: true });
      } else {
        skicka({ typ: "foto", beskrivning: "Instrumentfoto — inga värden kunde läsas", dataUrl: foto });
      }
    } catch {
      skicka({ typ: "foto", beskrivning: "Instrumentfoto — avläsningen kunde inte nås", dataUrl: foto });
    }
    setLaserAv(false);
  };

  const sparaAvlasning = () => {
    if (!avlasning) return;
    // Originalet först, sedan varje bekräftat värde som eget mätvärde —
    // strukturerad data ersätter aldrig originalevidensen.
    skicka({ typ: "foto", beskrivning: `Instrumentavläsning (${avlasning.tolkning.instrumenttyp})`, dataUrl: avlasning.foto });
    for (const v of avlasning.tolkning.varden) {
      // `kalla` sätts därför att värdet AVLÄSTES av en modell ur fotot
      // (TÜV T-5). Teknikern bekräftar innan något skrivs, vilket täcker
      // riktigheten — men bekräftelsen ger inte spårbarheten: utan
      // härkomst går ett maskinläst värde inte att skilja från ett
      // teknikern läste av instrumentet, och "hur kom siffran hit" är
      // just vad en granskare frågar om en avskriven avläsning.
      skicka({
        typ: "matvarde",
        beskrivning: v.beskrivning,
        varde: v.varde,
        enhet: v.enhet,
        kalla: "Instrumentavläsning (modelltolkad, bekräftad av tekniker)",
      });
    }
    paSparad?.(
      `Instrumentavläsning (${avlasning.tolkning.instrumenttyp}): ${avlasning.tolkning.varden
        .map((v) => `${v.beskrivning} = ${v.varde}${v.enhet ? ` ${v.enhet}` : ""}`)
        .join("; ")}`,
    );
    setAvlasning(null);
  };

  const spara = () => {
    const t = text.trim();
    if (typ === "observation" && t) {
      skicka({ typ: "observation", text: t });
      paSparad?.(`Observation: ${t}`);
    }
    if (typ === "kommentar" && t) {
      skicka({ typ: "kommentar", text: t });
      paSparad?.(t);
    }
    if (typ === "hypotes" && t) {
      skicka({ typ: "hypotes", text: t, niva: "lag" });
      paSparad?.(`Teknikerns hypotes (ej verifierad): ${t}`);
    }
    if (typ === "matvarde" && t && varde.trim()) {
      skicka({ typ: "matvarde", beskrivning: t, varde: varde.trim() });
      paSparad?.(`Mätvärde: ${t} = ${varde.trim()}`);
    }
    aterstall();
  };

  return (
    <Panel rubrik="Dokumentera">
      <div className="grid grid-cols-3 gap-2">
        {DOKTYPER.map((d) => (
          <button
            key={d.id}
            onClick={() =>
              d.id === "foto"
                ? filRef.current?.click()
                : d.id === "video"
                  ? videoRef.current?.click()
                  : d.id === "instrument"
                    ? instRef.current?.click()
                    : setTyp(typ === d.id ? null : d.id)
            }
            className={`min-h-9 border text-[12px] font-semibold transition-colors ${
              typ === d.id
                ? "border-[#00437A] bg-[#00437A] text-white"
                : "border-[#ADADAD] bg-[#F7F7F7] text-[#333333] hover:border-[#8FA8C0]"
            }`}
          >
            + {d.label}
          </button>
        ))}
      </div>
      <input
        ref={filRef}
        type="file"
        accept="image/*"
        capture="environment"
        className="hidden"
        onChange={async (e) => {
          const fil = e.target.files?.[0];
          if (!fil) return;
          const dataUrl = await skalaNerFoto(fil);
          skicka({ typ: "foto", beskrivning: "Foto", dataUrl });
          e.target.value = "";
        }}
      />
      <input
        ref={instRef}
        type="file"
        accept="image/*"
        capture="environment"
        className="hidden"
        onChange={(e) => {
          const fil = e.target.files?.[0];
          if (fil) lasAv(fil);
          e.target.value = "";
        }}
      />
      <input
        ref={videoRef}
        type="file"
        accept="video/*"
        capture="environment"
        className="hidden"
        onChange={async (e) => {
          const fil = e.target.files?.[0];
          e.target.value = "";
          if (!fil) return;
          setVideoFel("");
          try {
            setVideo({ dataUrl: await lasVideo(fil), beskrivning: "" });
          } catch (misslyckande) {
            setVideoFel(misslyckande instanceof Error ? misslyckande.message : "Videon kunde inte läsas.");
          }
        }}
      />
      {videoFel && <p className="mt-2 text-[12px] font-semibold text-[#8B1A1A]">{videoFel}</p>}
      {video && (
        <div className="mt-3 border border-[#C6C6C6] bg-white p-2">
          <video src={video.dataUrl} controls className="mb-2 max-h-48 w-full border border-[#C6C6C6]" />
          <TextFalt
            label="Vad visar videon? (obligatoriskt — det som låter eller rör sig)"
            varde={video.beskrivning}
            satt={(beskrivning) => setVideo((v) => (v ? { ...v, beskrivning } : v))}
            platshallare="T.ex. Motorljud vid tomgång — tickande från topplocket"
            rost
          />
          <div className="grid grid-cols-2 gap-2">
            <StorKnapp variant="sekundar" onClick={() => setVideo(null)}>
              Förkasta
            </StorKnapp>
            <StorKnapp
              disabled={!video.beskrivning.trim()}
              onClick={() => {
                skicka({ typ: "video", beskrivning: video.beskrivning.trim(), dataUrl: video.dataUrl });
                paSparad?.(`Video dokumenterad: ${video.beskrivning.trim()}`);
                setVideo(null);
              }}
            >
              Spara video
            </StorKnapp>
          </div>
        </div>
      )}
      {laserAv && <p className="mt-2 animate-pulse text-center text-[12px] font-semibold text-[#00437A]">Systemet läser av instrumentet …</p>}
      {avlasning && (
        <div className="mt-3 border border-[#C6C6C6] bg-white p-2">
          {avlasning.demo && (
            <p className="mb-2 border border-[#E0C36A] bg-[#FFF8E1] p-1.5 text-[11px] font-semibold text-[#9A6700]">
              Demo-avläsning — bildtolkningen kräver inloggning. Värdena är exempeldata.
            </p>
          )}
          <p className="mb-1 text-[11px] font-semibold uppercase tracking-wide text-[#4A5560]">
            Avläst: {avlasning.tolkning.instrumenttyp} — bekräfta innan något loggas
          </p>
          {avlasning.tolkning.varden.map((v, i) => (
            <div key={i} className="flex items-center justify-between gap-2 border-b border-[#EBEBEB] py-1 text-[13px] last:border-0">
              <span>
                {v.beskrivning}: <span className="font-semibold">{v.varde}{v.enhet ? ` ${v.enhet}` : ""}</span>
              </span>
              <span className={`text-[11px] font-semibold ${v.konfidens >= 0.95 ? "text-[#1E6B34]" : v.konfidens >= 0.8 ? "text-[#9A6700]" : "text-[#8B1A1A]"}`}>
                {v.konfidens >= 0.95 ? "✓" : `${Math.round(v.konfidens * 100)} %`}
              </span>
            </div>
          ))}
          <div className="mt-2 grid grid-cols-2 gap-2">
            <StorKnapp variant="sekundar" onClick={() => setAvlasning(null)}>
              Förkasta
            </StorKnapp>
            <StorKnapp onClick={sparaAvlasning}>Spara foto + värden</StorKnapp>
          </div>
        </div>
      )}
      {typ && typ !== "foto" && typ !== "instrument" && (
        <div className="mt-3">
          {typ === "hypotes" && (
            <p className="mb-2 text-[12px] font-semibold text-[#8B1A1A]">
              <IkonPunkt farg="#8B1A1A" /> En hypotes är en möjlig felorsak som kräver verifiering — den loggas aldrig som ett konstaterat fel.
            </p>
          )}
          <TextFalt
            label={typ === "matvarde" ? "Vad mättes?" : DOKTYPER.find((d) => d.id === typ)!.label}
            varde={text}
            satt={setText}
            platshallare={typ === "matvarde" ? "T.ex. Matningsspänning stift 30" : ""}
            rost
          />
          {typ === "matvarde" && <TextFalt label="Värde (med enhet)" varde={varde} satt={setVarde} platshallare="T.ex. 13,9 V" />}
          <StorKnapp disabled={!text.trim() || (typ === "matvarde" && !varde.trim())} onClick={spara}>
            Spara
          </StorKnapp>
        </div>
      )}
    </Panel>
  );
}

function OverlamningDialog({
  arende,
  metodik,
  nu,
  skicka,
  stang,
}: {
  arende: Arende;
  metodik: Metodik;
  nu: string;
  skicka: (h: Handelse) => void;
  stang: () => void;
}) {
  const anvandare = useFelsokning((s) => s.anvandare);
  const [till, setTill] = useState("");
  const [aiLage, setAiLage] = useState<"vilar" | "arbetar" | "fel" | "klar">("vilar");
  const [aiRader, setAiRader] = useState<{ typ: keyof typeof AI_RADTYP_LABEL; text: string }[]>([]);
  const text = overlamningstext(arende, metodik, nu);

  // Orkesterns sammanfattningsuppgift: risker och osäkerheter som inte
  // syns i checklistorna — loggas som AI-svar och visas i överlämningen.
  const komplettera = async () => {
    setAiLage("arbetar");
    try {
      const svar = await sammanfattaOverlamning(arende, metodik);
      if (svar) {
        skicka({ typ: "ai_svar", rader: svar.rader, nastaSteg: svar.nastaSteg, modell: svar.modell });
        setAiRader(svar.rader);
        setAiLage("klar");
      } else {
        setAiLage("vilar");
      }
    } catch {
      setAiLage("fel");
    }
  };

  return (
    <div className="fixed inset-0 z-30 flex items-end justify-center bg-black/70 p-4 sm:items-center">
      <div className="max-h-full w-full max-w-2xl overflow-y-auto border border-[#ADADAD] bg-[#F7F7F7] p-4">
        <h2 className="mb-3 text-[15px] font-semibold">Överlämningsrapport</h2>
        <pre className="mb-3 overflow-x-auto whitespace-pre-wrap bg-white p-4 text-[12px] text-[#333333]">
          {text}
        </pre>
        {aiLage === "vilar" && (
          <StorKnapp variant="sekundar" className="mb-3" onClick={komplettera}>
            Komplettera analysen: risker &amp; osäkerheter
          </StorKnapp>
        )}
        {aiLage === "arbetar" && <p className="mb-3 animate-pulse font-semibold text-[#00437A]">Systemet sammanfattar …</p>}
        {aiLage === "fel" && (
          <p className="mb-3 font-semibold text-[#8B1A1A]">Kunde inte hämtas — kräver inloggning. Överlämningen fungerar ändå.</p>
        )}
        {aiLage === "klar" && aiRader.length > 0 && (
          <div className="mb-3 bg-white p-4">
            {aiRader.map((rad, i) => (
              <p key={i} className="py-0.5 text-[12px] text-[#333333]">
                <span className="font-semibold text-[#4A5560]">{AI_RADTYP_LABEL[rad.typ]}:</span> {rad.text}
              </p>
            ))}
          </div>
        )}
        <TextFalt label="Lämnas till (valfritt)" varde={till} satt={setTill} platshallare="T.ex. Johan" />
        <div className="grid grid-cols-2 gap-2">
          <StorKnapp variant="sekundar" onClick={stang}>
            Avbryt
          </StorKnapp>
          <StorKnapp
            onClick={() => {
              skicka({ typ: "overlamning", fran: anvandare, till: till.trim() || undefined });
              stang();
            }}
          >
            Bekräfta överlämning
          </StorKnapp>
        </div>
      </div>
    </div>
  );
}

const NIVA_LABEL: Record<DelningsNiva, string> = {
  kund: "Kund",
  partner: "Extern partner",
  intern: "Intern",
};

// Live Share-behörighetsnivåer: skapa och återkalla delningslänkar.
// Nivåfiltreringen sker alltid på serversidan; verkstaden kontrollerar
// delningen och varje skapad/återkallad länk loggas i ärendet.
function DelningsHanterare({ arende, skicka }: { arende: Arende; skicka: (h: Handelse) => void }) {
  const [oppen, setOppen] = useState(false);
  const [delningar, setDelningar] = useState<Delning[]>([]);
  const [fel, setFel] = useState("");

  const uppdatera = async () => {
    try {
      setDelningar(await hamtaDelningar(arende.id));
      setFel("");
    } catch {
      setFel("Kunde inte hämta delningar — kräver inloggning och att ärendet är synkat.");
    }
  };

  if (!oppen) {
    return (
      <StorKnapp
        variant="sekundar"
        className="mt-2"
        onClick={() => {
          setOppen(true);
          uppdatera();
        }}
      >
        <IkonLank /> Delningslänkar (kund/partner/intern)
      </StorKnapp>
    );
  }

  return (
    <div className="mt-3 border border-[#C6C6C6] bg-white p-3">
      {fel && <p className="mb-2 font-semibold text-[#8B1A1A]">{fel}</p>}
      {delningar.map((delning) => (
        <div key={delning.kod} className="flex items-center justify-between gap-2 border-b border-[#DDDDDD] py-2 last:border-0">
          <span className="text-[14px]">
            {NIVA_LABEL[delning.niva]}{" "}
            {delning.aterkallad && <span className="text-[12px] font-semibold text-[#8B1A1A]">(återkallad)</span>}
          </span>
          {!delning.aterkallad && (
            <span className="flex gap-2">
              <button
                className="border border-[#ADADAD] px-3 py-1 font-semibold text-[#333333] hover:border-[#00437A]"
                onClick={() =>
                  navigator.clipboard.writeText(`${window.location.origin}/felsokning/delad/${delning.kod}`)
                }
              >
                Kopiera
              </button>
              <button
                className="border border-[#ADADAD] px-3 py-1 font-semibold text-[#8B1A1A] hover:border-[#8B1A1A]"
                onClick={async () => {
                  await aterkallaDelning(delning.kod);
                  skicka({ typ: "kommentar", text: `Delningslänk (${NIVA_LABEL[delning.niva]}) återkallad.` });
                  uppdatera();
                }}
              >
                Återkalla
              </button>
            </span>
          )}
        </div>
      ))}
      <div className="mt-2 grid grid-cols-3 gap-2">
        {(Object.keys(NIVA_LABEL) as DelningsNiva[]).map((niva) => (
          <button
            key={niva}
            className="min-h-9 border border-[#ADADAD] text-[12px] font-semibold text-[#333333] hover:border-[#00437A]"
            onClick={async () => {
              try {
                await skapaDelning(arende.id, niva);
                skicka({ typ: "kommentar", text: `Delningslänk skapad (${NIVA_LABEL[niva]}).` });
                uppdatera();
              } catch {
                setFel("Kunde inte skapa delning — kräver inloggning och att ärendet är synkat.");
              }
            }}
          >
            + {NIVA_LABEL[niva]}
          </button>
        ))}
      </div>
      <p className="mt-2 text-[11px] text-[#707070]">
        Kund: det kunddelbara · Extern partner: även hypoteser (märkta ej verifierade) · Intern: full insyn.
        Filtreringen sker på servern.
      </p>
    </div>
  );
}

function LoggFlik({ arende }: { arende: Arende }) {
  return (
    <Panel rubrik="Arbetslogg — append-only, ingenting skrivs över">
      <ol>
        {arende.handelser.map((post) => (
          <li key={post.id} className="flex gap-3 border-b border-[#DDDDDD] py-2 last:border-0">
            <span className="w-14 shrink-0 font-mono text-[12px] font-semibold text-[#00437A]">{tidKlockslag(post.tidpunkt)}</span>
            <div className="min-w-0">
              <p className="text-[13px] text-[#1A1A1A]">{handelseRubrik(post)}</p>
              <p className="text-[11px] text-[#707070]">{post.anvandare}</p>
              {post.handelse.typ === "foto" && (
                <Bild bilaga={post.handelse} alt={post.handelse.beskrivning} className="mt-1 max-h-40 border border-[#C6C6C6]" />
              )}
              {post.handelse.typ === "video" && (
                <Klipp bilaga={post.handelse} className="mt-1 max-h-40 border border-[#C6C6C6]" />
              )}
            </div>
          </li>
        ))}
      </ol>
    </Panel>
  );
}

function BriefFlik({
  arende,
  metodik,
  nu,
  skicka,
}: {
  arende: Arende;
  metodik: Metodik;
  nu: string;
  skicka: (h: Handelse) => void;
}) {
  const b = brief(arende, metodik, nu);
  const [granskning, setGranskning] = useState<"vilar" | "arbetar" | "fel">("vilar");
  const senasteAi = [...arende.handelser].reverse().find((p) => p.handelse.typ === "ai_svar");

  // Djupgranskningen går till orkesterns tyngsta modell (Opus 5, hög
  // effort): motsägelser, luckor och förhastade slutsatser i hela loggen.
  const granska = async () => {
    setGranskning("arbetar");
    try {
      const svar = await granskaUnderlag(arende, metodik);
      if (svar) skicka({ typ: "ai_svar", rader: svar.rader, nastaSteg: svar.nastaSteg, modell: svar.modell });
      setGranskning("vilar");
    } catch {
      setGranskning("fel");
    }
  };

  return (
    <>
      {!b.avslutat && (
        <Panel rubrik="Granskning av underlaget">
          <p className="mb-2 text-[#333333]">
            Låt systemet gå igenom hela underlaget innan du lämnar det vidare: motsägelser mellan
            observationer, luckor i dokumentationen och slutsatser som saknar stöd.
          </p>
          {granskning === "arbetar" && <p className="mb-2 animate-pulse font-semibold text-[#00437A]">Systemet granskar underlaget …</p>}
          {granskning === "fel" && (
            <p className="mb-2 font-semibold text-[#8B1A1A]">Granskningen kunde inte hämtas — kräver inloggning. Försök igen.</p>
          )}
          {senasteAi && senasteAi.handelse.typ === "ai_svar" && granskning === "vilar" && (
            <div className="mb-2">
              {senasteAi.handelse.rader.map((rad, i) => (
                <p key={i} className="py-0.5 text-[14px]">
                  <span className="font-semibold text-[#4A5560]">{AI_RADTYP_LABEL[rad.typ]}:</span> {rad.text}
                </p>
              ))}
              <p className="mt-1 text-[14px]">
                <span className="font-semibold text-[#00437A]">Nästa steg:</span> {senasteAi.handelse.nastaSteg}
              </p>
              <p className="mt-1 text-[11px] text-[#707070]">Senaste analys ({senasteAi.handelse.modell})</p>
            </div>
          )}
          <StorKnapp variant="sekundar" disabled={granskning === "arbetar"} onClick={granska}>
            <IkonSok /> Granska underlaget
          </StorKnapp>
        </Panel>
      )}
      <Panel rubrik="Objekt">
        {b.objekt ? (
          <>
            <p className="text-[15px] font-semibold">{b.objekt.beskrivning}</p>
            <p className="font-semibold text-[#00437A]">{b.objekt.identifierare}</p>
            {b.objekt.kund && <p className="text-[#333333]">Kund: {b.objekt.kund}</p>}
            {b.ansvarig && <p className="text-[#333333]">Ansvarig tekniker: {b.ansvarig}</p>}
          </>
        ) : (
          <p className="text-[#4A5560]">Ej identifierat</p>
        )}
      </Panel>
      <Panel rubrik="Kundens beskrivning">
        <p className="text-[14px]">{b.felbeskrivning ? `”${b.felbeskrivning}”` : "—"}</p>
      </Panel>
      <Panel rubrik="Utförda kontroller">
        {b.utfordaKontroller.length === 0 && <p className="text-[#4A5560]">Inga ännu.</p>}
        {b.utfordaKontroller.map((k, i) =>
          k.undantag ? (
            <p key={i} className="py-0.5 text-[14px] text-[#9A6700]">
              <IkonVarning /> {k.text} — underlag saknas: {k.undantag}
            </p>
          ) : (
            <p key={i} className="py-0.5 text-[14px]">
              ✓ {k.text}
              {k.resultat && <span className="text-[#4A5560]"> — {k.resultat}</span>}
            </p>
          ),
        )}
      </Panel>
      <Panel rubrik="Observationer">
        {b.observationer.length === 0 && <p className="text-[#4A5560]">Inga ännu.</p>}
        {b.observationer.map((o, i) => (
          <p key={i} className="py-0.5 text-[14px]">• {o}</p>
        ))}
      </Panel>
      {b.hypoteser.length > 0 && (
        <Panel rubrik="Hypoteser — kräver verifiering">
          {b.hypoteser.map((h, i) => (
            <p key={i} className="py-0.5 text-[14px]">
              <NivaBadge niva={h.niva} /> {h.text}
            </p>
          ))}
        </Panel>
      )}
      <Panel rubrik="Ej kontrollerat">
        {b.ejKontrollerat.length === 0 && <p className="text-[#4A5560]">Allt i metodiken är utfört.</p>}
        {b.ejKontrollerat.map((e, i) => (
          <p key={i} className="py-0.5 text-[14px] text-[#333333]">– {e}</p>
        ))}
      </Panel>
      {!b.avslutat && (
        <Panel rubrik="Rekommenderat nästa steg">
          {b.rekommenderatNastaSteg.map((s, i) => (
            <p key={i} className="py-0.5 text-[14px]">{i + 1}. {s}</p>
          ))}
        </Panel>
      )}
      <Panel rubrik="Tillförlitlighet">
        {b.tillforlitlighet.map((r, i) => (
          <p key={i} className="py-0.5 text-[14px]">
            <NivaBadge niva={r.niva} /> {r.text}
          </p>
        ))}
      </Panel>
      <Panel rubrik="Total arbetstid">
        <p className="text-[17px] font-semibold">{b.totalArbetstid}</p>
      </Panel>
    </>
  );
}

// Kundrapporten: en tydlig tidslinje i stället för "Felsökning – 2,5 timmar".
// Interna poster (kategoribyten) visas inte för kund.
function RapportFlik({
  arende,
  metodik,
  nu,
  skicka,
}: {
  arende: Arende;
  metodik: Metodik;
  nu: string;
  skicka: (h: Handelse) => void;
}) {
  const anvandare = useFelsokning((s) => s.anvandare);
  const b = brief(arende, metodik, nu);
  const bilder = foton(arende);
  const klipp = videor(arende);
  const fordelning = tidsfordelningsRader(arende, nu);
  const idn = arendeidentitet(arende);
  const typ = arendetyp(arende);
  const repro = reproducering(arende);
  const orsakerLista = felorsaker(arende);
  const atgardsRader = atgarder(arende);
  const kkRad = kvalitetskontroll(arende);
  const forslagRader = atgardsforslag(arende);
  const beslutRad = kundbeslut(arende);
  let matIn: string | undefined;
  let matUt: string | undefined;
  for (const p of arende.handelser) {
    const h = p.handelse;
    if (h.typ === "matarstallning" && !h.undantag) {
      if (h.lage === "ingaende") matIn = h.varde;
      else matUt = h.varde;
    }
  }
  // Kategoribyten är interna; hypoteser och AI-dialogen är arbetsmaterial
  // och ingår inte i det som delas med kund.
  const kundposter = arende.handelser.filter(
    (p) => !["kategori_byte", "hypotes", "ai_svar", "ansvarig_satt", "arbetsorder_skannad"].includes(p.handelse.typ),
  );

  // Alla exporter bygger på samma händelselogg och versionsmärks:
  // version = antal händelser vid exporttillfället. Spårbarhetspaketet
  // (ECM-version, evidensposter med hash, grindstatus) följer med.
  const exporteraJson = () => {
    const version = arende.handelser.length;
    const data = {
      export: { format: "JSON", version, exporteradAv: anvandare, tidpunkt: new Date().toISOString() },
      ecm: sparbarhetspaket(arende, metodik),
      arende,
    };
    const blob = new Blob([JSON.stringify(data, null, 2)], { type: "application/json" });
    const url = URL.createObjectURL(blob);
    const lank = document.createElement("a");
    lank.href = url;
    lank.download = `arende-${arende.nummer}-v${version}.json`;
    lank.click();
    URL.revokeObjectURL(url);
    skicka({ typ: "export_skapad", format: "JSON", version });
  };

  const grind = kvalitetsgrind(arende, metodik);
  const godkand = grindGodkand(arende, metodik);

  return (
    <>
      {/* ECM-kvalitetsgrinden: slutrapporten kan inte genereras förrän
          varje obligatoriskt påstående har evidens eller dokumenterat
          undantag i händelseloggen. */}
      <Panel rubrik={`Kvalitetsgrind — ECM v${ECM_VERSION}`}>
        {grind.map((rad) => (
          <div key={rad.id} className="border-b border-[#EBEBEB] py-1 last:border-0">
            <p className="text-[13px]">
              <span className={rad.ok ? "text-[#1E6B34]" : rad.kravs ? "text-[#8B1A1A]" : "text-[#9A6700]"}>
                {rad.ok ? <IkonCheck /> : rad.kravs ? <IkonKryss /> : <IkonVarning />}
              </span>{" "}
              {rad.rubrik}
              {!rad.kravs && <span className="text-[11px] text-[#707070]"> (rekommenderas)</span>}
            </p>
            {rad.detalj && <p className="pl-6 text-[12px] text-[#707070]">{rad.detalj}</p>}
          </div>
        ))}
        {!godkand && (
          <p className="mt-2 text-[12px] font-semibold text-[#8B1A1A]">
            Rapporten kan inte genereras förrän varje underkänd rad har evidens eller ett dokumenterat undantag
            (”Underlag kan inte tas fram” i guiden).
          </p>
        )}
      </Panel>
      <Panel rubrik="Kundrapport">
        <p className="mb-2 text-[#333333]">
          Delningsbar sammanställning av utfört arbete. Granska innehållet innan rapporten delas — bilder kan
          innehålla uppgifter om andra kunder.
        </p>
        <div className="grid grid-cols-2 gap-2">
          <StorKnapp variant="sekundar" disabled={!godkand} onClick={() => window.print()}>
            Skriv ut / PDF
          </StorKnapp>
          <StorKnapp variant="sekundar" onClick={exporteraJson}>
            Exportera JSON
          </StorKnapp>
        </div>
        <Link to={`/felsokning/dela/${arende.id}`} className="mt-2 block">
          <StorKnapp variant="sekundar"><IkonPunkt farg="#1E6B34" /> Öppna Live Share-vy</StorKnapp>
        </Link>
        {plattformAktiv() ? (
          <DelningsHanterare arende={arende} skicka={skicka} />
        ) : (
          arende.delningskod && (
            <StorKnapp
              variant="sekundar"
              className="mt-2"
              onClick={() => {
                navigator.clipboard.writeText(`${window.location.origin}/felsokning/delad/${arende.delningskod}`);
                skicka({ typ: "kommentar", text: "Delningslänk kopierad för extern mottagare." });
              }}
            >
              <IkonLank /> Kopiera delningslänk
            </StorKnapp>
          )
        )}
        <p className="mt-2 text-[12px] text-[#707070]">
          Delningslänken kräver att ärendet är synkat mot molnet (inloggad användare).
        </p>
      </Panel>
      {/* ALVA-PROC-0030 · Sammanfattningen först. Den som öppnar
          rapporten — kund, handläggare, nästa tekniker — ska få bilden
          på fem sekunder och sedan kunna gå djupare. Texten är härledd
          ur loggen, så den kan inte säga något som inte står där. */}
      <Panel rubrik="Sammanfattning">
        <p className="text-[15px] leading-[24px] text-[#1B1E22]">{sammanfatta(arende).text}</p>
        <p className="mt-2 font-mono text-[11px] text-[#4D5662]">ALVA-PROC-0030 · härledd ur händelseloggen</p>
      </Panel>

      {/* ALVA-RULE-200 · Teknikerns varför. Placerad före underlaget:
          en handläggare läser skälet först och kontrollerar det sedan. */}
      {(() => {
        const s = [...arende.handelser].reverse().find((p) => p.handelse.typ === "slutsats");
        if (!s || s.handelse.typ !== "slutsats") return null;
        const h = s.handelse;
        return (
          <Panel rubrik="Slutsats och motivering">
            {identitetsRader([
              [h.orsakFastställd === false ? "Skäl till att orsaken inte fastställts" : "Motivering", h.motivering],
              ["Uteslutna alternativ", h.uteslutet],
              ["Val av åtgärd", h.atgardsval ?? "—"],
              ["Kvarstående osäkerhet", h.kvarstaende],
            ])}
          </Panel>
        );
      })()}

      {/* Rapportens första sida: ärendeidentiteten — registrerad en gång,
          återanvänd här automatiskt. */}
      <Panel rubrik="Ärendeinformation">
        {identitetsRader([
          ["Ärende", `#${arende.nummer}`],
          ["Arbetsorder", idn.arbetsorder],
          ["Claim-/garantinr", idn.claim],
          ["Skadenummer", idn.skadenummer],
          ["Ärendetyp", typ],
          ["Ansvarig tekniker", idn.ansvarig],
        ])}
      </Panel>
      <Panel rubrik="Fordonsinformation">
        {identitetsRader([
          ["Personbil", idn.beskrivning],
          ["Regnr", idn.identifierare],
          ["VIN", idn.vin],
          ["Mätarställning in", matIn],
          ["Mätarställning ut", matUt],
          ["Kund", idn.kund],
        ])}
      </Panel>
      {/* Beviskedjan: kundens upplevelse, teknikerns verifiering och den
          tekniska slutsatsen hålls strikt åtskilda och spårbara. */}
      <Panel rubrik="Kundens beskrivning">
        <p className="text-[14px]">{b.felbeskrivning ? `”${b.felbeskrivning}”` : "—"}</p>
      </Panel>
      <Panel rubrik="Verifierad observation">
        {repro ? (
          <>
            <p className="text-[14px] font-semibold">{reproduceringsText(repro.status)}</p>
            <p className="text-[13px] text-[#4A5560]">{repro.beskrivning}</p>
          </>
        ) : (
          <p className="text-[14px] text-[#4A5560]">Reproducering ej dokumenterad ännu.</p>
        )}
      </Panel>
      <Panel rubrik="Felorsaksanalys">
        {orsakerLista.length === 0 && <p className="text-[14px] text-[#4A5560]">Felorsaksanalys ej dokumenterad ännu.</p>}
        {orsakerLista.map((p) => {
          const h = p.handelse;
          if (h.typ !== "felorsak") return null;
          return (
            <div key={p.id} className="mb-3 border-b border-[#EBEBEB] pb-2 last:mb-0 last:border-0">
              <p className="text-[14px] font-semibold">{h.avvikelse}</p>
              {identitetsRader([
                ["Bedömd grundorsak", h.orsaker.join(", ")],
                ["Underlag för bedömningen", h.underlag.join(", ")],
                ["Säkerhetsnivå", TILLFORLITLIGHET_LABEL[h.sakerhet]],
                ["Rekommenderad åtgärd", h.atgard],
                ["Motivering", h.motivering],
                ["Stärkande kontroller", h.ytterligareKontroller],
              ])}
            </div>
          );
        })}
      </Panel>
      {(forslagRader.length > 0 || beslutRad) && (
        <Panel rubrik="Åtgärdsförslag och kundens besked">
          {forslagRader.map((p) => {
            const h = p.handelse;
            if (h.typ !== "atgardsforslag") return null;
            return (
              <div key={p.id}>
                {identitetsRader([
                  ["Föreslagen åtgärd", h.beskrivning],
                  ["Uppskattad kostnad", h.uppskattadKostnad],
                  ["Uppskattad tid", h.uppskattadTid],
                ])}
              </div>
            );
          })}
          {beslutRad &&
            identitetsRader([
              ["Kundens besked", KUNDBESLUT_LABEL[beslutRad.beslut]],
              ["Lämnat via", beslutRad.kanal],
              ["Kommentar", beslutRad.kommentar],
            ])}
        </Panel>
      )}
      <Panel rubrik="Utförd åtgärd och verifiering">
        {atgardsRader.length === 0 && <p className="text-[14px] text-[#4A5560]">Ingen åtgärd dokumenterad ännu.</p>}
        {atgardsRader.map((p) => {
          const h = p.handelse;
          if (h.typ !== "atgard_utford") return null;
          return (
            <div key={p.id} className="mb-2">
              {h.utford ? (
                identitetsRader([
                  ["Utförd åtgärd", h.beskrivning],
                  ["Delar", h.delar],
                ])
              ) : (
                <p className="text-[14px] text-[#9A6700]">Ingen åtgärd utförd — {h.motivering}</p>
              )}
            </div>
          );
        })}
        {kkRad &&
          identitetsRader([
            ["Kvalitetskontroll", KVALITETSKONTROLL_LABEL[kkRad.resultat]],
            ["Verifiering", kkRad.beskrivning],
          ])}
      </Panel>
      <Panel rubrik="Sammanfattning">
        <p className="text-[14px]">Total arbetstid: <span className="font-semibold">{b.totalArbetstid}</span></p>
        {fordelning.length > 0 && (
          <div className="mt-2">
            {fordelning.map((r) => (
              <p key={r.label} className="text-[#333333]">{r.label}: {r.tid}</p>
            ))}
          </div>
        )}
      </Panel>
      <Panel rubrik="Tidslinje">
        {kundposter.map((post) => (
          <p key={post.id} className="py-0.5 text-[13px]">
            <span className="font-mono font-semibold text-[#00437A]">{tidKlockslag(post.tidpunkt)}</span>{" "}
            {handelseRubrik(post)}
          </p>
        ))}
      </Panel>
      {klipp.length > 0 && (
        <Panel rubrik="Dokumenterad video">
          {klipp.map((v, i) => (
            <figure key={i} className="mb-2">
              <Klipp bilaga={v.bilaga} className="w-full border border-[#C6C6C6]" />
              <figcaption className="mt-1 text-[11px] text-[#4A5560]">{v.beskrivning}</figcaption>
            </figure>
          ))}
        </Panel>
      )}
      {bilder.length > 0 && (
        <Panel rubrik="Dokumenterade bilder">
          <div className="grid grid-cols-2 gap-2">
            {bilder.map((bild, i) => (
              <figure key={i}>
                <Bild bilaga={bild.bilaga} alt={bild.beskrivning} className="border border-[#C6C6C6]" />
                <figcaption className="mt-1 text-[11px] text-[#4A5560]">{bild.beskrivning}</figcaption>
              </figure>
            ))}
          </div>
        </Panel>
      )}
      {!b.avslutat || b.ejKontrollerat.length > 0 ? (
        <Panel rubrik="Rekommenderade nästa steg">
          {(b.avslutat ? b.ejKontrollerat : b.rekommenderatNastaSteg).map((s, i) => (
            <p key={i} className="py-0.5 text-[14px]">{i + 1}. {s}</p>
          ))}
        </Panel>
      ) : null}
      <p className="text-center text-[11px] text-[#8A8A8A]">
        Genererad ur ärendets händelselogg · Observationer och mätvärden redovisas utan slutsatser som saknar stöd.
      </p>
      <Ritningsstampel arende={arende} />
      <Felanmalan arende={arende} metodik={metodik ?? null} />
    </>
  );
}

/**
 * Felanmälan — i ärendet, inte någon annanstans.
 *
 * En anmälningsknapp som bara finns i en portalvy nås aldrig av den som
 * står med händerna i en bil. Den ligger därför i varje ärende, och tar
 * med sitt sammanhang självt: ärende, metodik, öppet steg och
 * plattformsversion. Teknikern minns inte plattformsversionen, och att
 * fråga efter den är att lägga över vårt problem på verkstaden.
 *
 * Anmälan lagras aldrig i ärendets händelselogg. Loggen är protokollet
 * över vad som gjordes med FORDONET; att ett fel i vårt system anmäldes
 * hör inte dit, och skulle följa med in i en rapport som ska hålla i en
 * tvist.
 */
function Felanmalan({ arende, metodik }: { arende: Arende; metodik: Metodik | null }) {
  const [oppen, setOppen] = useState(false);
  const [typ, setTyp] = useState("felanmalan");
  const [rubrik, setRubrik] = useState("");
  const [text, setText] = useState("");
  const [utfall, setUtfall] = useState<{ ok?: string; fel?: string }>({});
  const [skickar, setSkickar] = useState(false);

  const skicka = async () => {
    setSkickar(true);
    setUtfall({});
    try {
      const { anmalSupport, plattformAktiv } = await import("@/felsokning/plattform");
      if (!plattformAktiv()) {
        setUtfall({ fel: "Ingen plattform är konfigurerad — anmälan kan inte skickas härifrån." });
        return;
      }
      const { beteckning } = await anmalSupport({
        typ,
        rubrik,
        beskrivning: text,
        arendeId: arende.id,
        sammanhang: {
          metodikId: metodik?.id ?? "",
          plattformsversion: PLATTFORMSVERSION,
          klient: typeof navigator === "undefined" ? "" : navigator.userAgent,
        },
      });
      setUtfall({ ok: beteckning });
      setRubrik("");
      setText("");
    } catch (orsak) {
      setUtfall({ fel: orsak instanceof Error ? orsak.message : String(orsak) });
    } finally {
      setSkickar(false);
    }
  };

  if (!oppen) {
    return (
      <p className="mt-4 text-center">
        <button
          type="button"
          onClick={() => setOppen(true)}
          className="text-[12px] uppercase tracking-wide text-[#4A5560] underline"
        >
          Rapportera ett problem med systemet
        </button>
      </p>
    );
  }

  return (
    <Panel rubrik="Rapportera ett problem med systemet">
      <p className="mb-2 text-[12px] text-[#707070]">
        Ärende, metodik och plattformsversion följer med automatiskt. Inget om fordonet eller kunden
        skickas.
      </p>
      <div className="mb-2 grid grid-cols-3 gap-2">
        {[
          ["felanmalan", "Fel"],
          ["fraga", "Fråga"],
          ["forbattring", "Förbättring"],
        ].map(([id, etikett]) => (
          <StorKnapp key={id} variant={typ === id ? "primar" : "sekundar"} onClick={() => setTyp(id)}>
            {etikett}
          </StorKnapp>
        ))}
      </div>
      <TextFalt label="Vad gäller det?" varde={rubrik} satt={setRubrik} platshallare="Kort rubrik" />
      <TextFalt
        label="Vad hände, och vad väntade du dig?"
        varde={text}
        satt={setText}
        platshallare="Den som svarar ser inte din skärm."
        flerRad
      />
      {utfall.ok && <p className="mt-2 text-[13px] text-[#1E6B34]">Anmäld som {utfall.ok}.</p>}
      {utfall.fel && <p className="mt-2 text-[13px] text-[#8B1A1A]">{utfall.fel}</p>}
      <div className="mt-2 grid grid-cols-2 gap-2">
        <StorKnapp onClick={skicka} disabled={skickar || rubrik.trim().length < 5 || text.trim().length < 20}>
          {skickar ? "Skickar" : "Skicka anmälan"}
        </StorKnapp>
        <StorKnapp variant="sekundar" onClick={() => setOppen(false)}>
          Stäng
        </StorKnapp>
      </div>
    </Panel>
  );
}

/**
 * Ritningsstämpel.
 *
 * En teknisk ritning bär alltid ett hörn med beteckning, revision och
 * status, så att ett papper på en verkstadsbänk går att härleda till
 * exakt vilken utgåva det kom ur. En rapport som ska hålla i en tvist
 * behöver samma sak: läsaren måste kunna se vilket regelpaket och vilken
 * evidensmodell som gällde när ärendet stängdes — inte vilka som gäller
 * den dag rapporten läses.
 *
 * Varje fält är något en granskare efterfrågar innan den litar på resten
 * av dokumentet. Inget av dem är dekoration.
 */
function Ritningsstampel({ arende }: { arende: Arende }) {
  const paket = aktivtRegelpaket();
  const avslutad = [...arende.handelser].reverse().find((p) => p.handelse.typ === "arende_avslutat");
  const falt: [string, string][] = [
    ["Beteckning", arendebeteckning(arende.nummer)],
    ["Regelpaket", paket.version],
    ["Evidensmodell", `ECM ${ECM_VERSION}`],
    // Versionen VID AVSLUTET, inte dagens. Stämpeln läste tidigare den
    // aktuella konstanten, så ett ärende stängt under 1.0 visade 3.1 —
    // vilket är exakt det stämpelns beskrivning ovan säger att den inte
    // ska göra. Ett öppet ärende visar dagens, eftersom det är sant då.
    ["Plattform", avslutad ? versionVidAvslut(avslutad.handelse) : PLATTFORMSVERSION],
    ["Status", avslutad ? "Closed" : "In progress"],
    ["Utgåva", avslutad ? new Date(avslutad.tidpunkt).toISOString().slice(0, 10) : "—"],
  ];

  return (
    <div className="alva-yta mt-3 border border-[#C6C6C6] bg-white">
      <div className="grid grid-cols-2 sm:grid-cols-3">
        {falt.map(([etikett, varde]) => (
          <div key={etikett} className="border-b border-r border-[#DDDDDD] px-2 py-2 last:border-r-0">
            <div className="text-[10px] font-semibold uppercase tracking-[0.08em] text-[#4A5560]">{etikett}</div>
            <div className="mt-2 font-mono text-[11px] text-[#1B1E22]">{varde}</div>
          </div>
        ))}
      </div>
    </div>
  );
}
