// Kvalitetsgrinden på serversidan.
//
// Revisionen (docs/QUALITY-AUDIT.md, C-2) fann att grinden bara fanns i
// webbläsaren. Ett ärende kunde därmed avslutas utan evidens genom ett
// anrop till API:t, och rapporten presenterade det som komplett — trots
// att dokumentationen påstod att systemet aldrig kan skriva en slutsats
// ECM inte godkänt.
//
// Modulen utvärderar de obligatoriska raderna direkt mot händelseloggen.
// Klientens ecm.ts har fler rader (råd, rekommendationer, härledd
// evidensnivå per påstående) och fortsätter vara vägledningen i
// gränssnittet. Det som blockerar ett avslut bor här.
//
// Avsiktligt: reglerna är rena funktioner av (händelser, metodik). Ingen
// databasåtkomst, ingen tid, inget slumpmässigt — samma logg ger alltid
// samma utfall, vilket är vad som gör en spärr granskbar.

import { granskaSlutsats } from "./motivering.mjs";

const UNDANTAG_MOTIVERAT = (h) => typeof h.undantag === "string" && h.undantag.trim().length > 0;

// Frågor som måste besvaras jakande för att arbetet ska få avslutas.
// Speglar SPARRFRAGOR i app/src/felsokning/metodik.ts; ett test jämför
// listorna så de inte kan glida isär.
export const SPARRFRAGOR = {
  "hogvolt/sakerhet/behorighet": "Behörighet för högvoltsarbete bekräftad",
  "hogvolt/sakerhet/avstangt": "Fordonet spänningslöst enligt tillverkarens rutin",
};

/** Händelser av en viss typ. */
const av = (handelser, typ) => handelser.filter((h) => h.typ === typ);

/**
 * Utvärderar avslutsgrinden.
 *
 * @returns lista med hinder; tom lista betyder att ärendet får avslutas.
 */
export function grinda(handelser, metodik) {
  const hinder = [];
  const krav = (id, rubrik, ok, detalj) => {
    if (!ok) hinder.push(detalj ? { id, rubrik, detalj } : { id, rubrik });
  };

  krav("objekt", "Fordons-/objektidentifiering verifierad", handelser.some((h) => h.typ === "objekt_identifierat"));

  // Historiken får besvaras nekande, men då krävs en motivering — annars
  // vore "Nej" ett sätt att hoppa över kontrollen utan att det syns.
  const historik = av(handelser, "historik_kontrollerad").at(-1);
  krav(
    "historik",
    "Fordonshistorik kontrollerad eller motiverad",
    Boolean(historik) && (historik.kontrollerad || (historik.kommentar ?? "").trim().length > 0),
    historik ? "Nekad historikkontroll kräver en motivering." : "Ingen historikkontroll dokumenterad.",
  );

  // Mätarställningen ska vara FOTOGRAFERAD, inte bara inskriven.
  //
  // Skillnaden är hela dess bevisvärde. En inskriven siffra är teknikerns
  // påstående om vad som stod på mätaren; ett foto visar vad som stod
  // där. Mätarställningen är samtidigt det som avgör garanti- och
  // försäkringsfrågor i efterhand, och den enda uppgift i ärendet som
  // någon kan ha ett intresse av att skriva fel.
  //
  // Klienten krävde redan fotot — men bara klienten, och en regel som
  // bara finns i gränssnittet är en vana, inte en spärr. Det är samma
  // förhållande som QUALITET C-2 gällde, fast åt andra hållet: här var
  // det servern som var svagare, vilket är värre, eftersom servern är
  // den som är auktoritativ.
  //
  // Undantag finns och måste finnas — en trasig display, en timräknare
  // som sitter oåtkomligt, en spegling som inte går att fota bort. Men
  // undantaget kräver en motivering, av samma skäl som ett nekat
  // historiksvar gör det.
  for (const [lage, rubrik] of [
    ["ingaende", "Ingående mätarställning fotograferad"],
    ["utgaende", "Utgående mätarställning fotograferad"],
  ]) {
    const poster = av(handelser, "matarstallning").filter((h) => h.lage === lage);
    const dokumenterad = poster.length > 0;
    const fotograferad = poster.some((h) => h.bilagaId || h.dataUrl);
    const motiverat = poster.some((h) => (h.undantag ?? "").trim().length > 0);
    krav(
      `matarstallning_${lage}`,
      rubrik,
      dokumenterad && (fotograferad || motiverat),
      !dokumenterad
        ? "Ingen mätarställning dokumenterad."
        : "Mätarställningen är inskriven men inte fotograferad. Fotografera mätaren eller ange varför det inte går.",
    );
  }

  krav(
    "reproducering",
    "Symptomverifiering: reproducerat eller dokumenterat ej reproducerbart",
    handelser.some((h) => h.typ === "reproducering"),
  );

  krav("felorsak", "Felorsaksanalys dokumenterad", handelser.some((h) => h.typ === "felorsak"));

  // Åtgärdskedjan. En utebliven åtgärd är ett giltigt utfall — men bara
  // när skälet står i loggen.
  const atgarder = av(handelser, "atgard_utford");
  const utfordArbete = atgarder.some((h) => h.utford === true);
  krav(
    "atgard",
    "Åtgärd dokumenterad eller motiverad",
    atgarder.length > 0,
    "Varken utförd åtgärd eller skäl till att den uteblev finns dokumenterat.",
  );

  if (utfordArbete) {
    const beslut = av(handelser, "kundbeslut");
    krav("kundbeslut", "Kundens besked på åtgärdsförslaget registrerat", beslut.length > 0);

    // Hårt fel, inte en varning: arbete utfört trots att kunden avböjt.
    krav(
      "avbojt_men_utfort",
      "Utfört arbete trots avböjt åtgärdsförslag",
      !beslut.some((h) => h.beslut === "avbojt"),
      "Kunden avböjde förslaget men arbete har dokumenterats som utfört.",
    );

    krav(
      "kvalitetskontroll",
      "Kvalitetskontroll genomförd — symptomet verifierat",
      handelser.some((h) => h.typ === "kvalitetskontroll"),
    );
  }

  // Metodikens kontroller: varje kontroll ska bära evidens eller ett
  // dokumenterat undantag. Det är den rad som gör metodiken bindande.
  const utforda = new Map();
  for (const h of av(handelser, "kontroll_utford")) utforda.set(`${h.stegId}/${h.kontrollId}`, h);

  const saknade = [];
  const fotokravande = [];
  const foton = av(handelser, "foto").length;
  for (const steg of metodik?.steg ?? []) {
    for (const kontroll of steg.kontroller ?? []) {
      const h = utforda.get(`${steg.id}/${kontroll.id}`);
      if (!h) {
        saknade.push(`${steg.rubrik} · ${kontroll.text}`);
        continue;
      }
      if (UNDANTAG_MOTIVERAT(h)) continue;

      // En fotokontroll verifieras av fotot. Regeln krävde tidigare ett
      // textresultat även här — trots att gränssnittet märker fältet
      // "Observation (valfritt)". Grinden nekade därmed avslut på nästan
      // varje riktigt ärende, vilket inte syntes så länge klienten hade
      // ett eget och mildare villkor (QUALITY-AUDIT-2 · M-7).
      if (kontroll.krav === "foto") {
        fotokravande.push(kontroll.text);
        continue;
      }
      if (!(h.resultat ?? "").trim()) saknade.push(`${steg.rubrik} · ${kontroll.text} (utan resultat)`);
    }
  }
  krav(
    "metodik_kontroller",
    "Metodikens kontroller: evidens eller dokumenterat undantag",
    saknade.length === 0,
    saknade.slice(0, 5).join(" · "),
  );
  krav(
    "foton",
    "Foton finns för fotokrävande kontroller",
    foton >= fotokravande.length,
    fotokravande.length ? `${fotokravande.length} kontroller kräver foto, ${foton} foton i loggen.` : undefined,
  );

  // ALVA-RULE-200 · Slutsatsen. Ett ärende stängs aldrig utan att
  // teknikern lämnat ett varför — motiveringen som knyter slutsatsen
  // till underlaget, vad som uteslöts, och vad som kvarstår osäkert.
  //
  // Detta är den rad som saknas i varje verkstadsprotokoll: underlaget
  // säger vad som mättes, slutsatsen vad som är fel, men ingenting
  // varför det ena medför det andra. Det är precis det steget en
  // garantihandläggare eller försäkringsbedömare behöver granska.
  const slutsats = av(handelser, "slutsats").at(-1);
  for (const brist of granskaSlutsats(slutsats, handelser)) {
    hinder.push({ id: `slutsats_${brist.falt}`, rubrik: brist.text });
  }

  // Säkerhetsspärrar. Ett nekande svar på en spärrfråga får aldrig
  // passera som "besvarad" — se SPARRFRAGOR i klientens metodik.ts.
  // Klientens spärr är vägledning; den här är hindret (QUALITET M-2).
  for (const [nyckel, text] of Object.entries(SPARRFRAGOR)) {
    const [metodikId, stegId, frageId] = nyckel.split("/");
    if (metodik?.id !== metodikId) continue;
    const svaret = av(handelser, "fraga_besvarad").findLast(
      (h) => h.stegId === stegId && h.frageId === frageId,
    );
    krav(`sparr_${frageId}`, text, svaret?.svar === "Ja", "Säkerhetskravet är inte uppfyllt.");
  }

  // Evidensnivån måste överstiga E0 — annars avslutas ett ärende utan
  // att någonting alls har dokumenterats.
  krav(
    "evidens",
    "Evidensnivå över E0",
    handelser.some((h) => ["foto", "video", "matvarde", "observation", "kontroll_utford"].includes(h.typ)),
    "Ingen evidens av något slag finns i loggen.",
  );

  return hinder;
}

/**
 * Extra krav som följer av ärendetypen. Reglerna kommer från regelpaketet
 * (ECM Knowledge Library) och är alltså data, inte kod.
 */
export function grindaArendetyp(handelser, regelpaket) {
  const typ = av(handelser, "arendetyp_satt").at(-1)?.arendetyp;
  const regler = regelpaket?.arendetyper?.[typ]?.krav ?? [];
  const hinder = [];

  const uppfyllt = {
    miltal: () => av(handelser, "matarstallning").length > 0,
    historik: () => av(handelser, "historik_kontrollerad").some((h) => h.kontrollerad),
    foto: () => av(handelser, "foto").length > 0,
    claim: () => harReferens(handelser, ["ao_claim", "claim"]),
    skadenummer: () => harReferens(handelser, ["ao_skadenummer", "skadenummer"]),
  };

  for (const regel of regler) {
    const kontroll = uppfyllt[regel.typ ?? regel];
    // En okänd kravtyp får aldrig tolkas som uppfylld. Ett regelpaket som
    // inte går att utvärdera ska stoppa avslutet, inte släppa igenom det.
    if (!kontroll) {
      hinder.push({ id: `arendetyp_${regel.typ ?? regel}`, rubrik: `Okänt krav i regelpaketet: ${regel.typ ?? regel}` });
    } else if (!kontroll()) {
      hinder.push({ id: `arendetyp_${regel.typ ?? regel}`, rubrik: regel.rubrik ?? `Krav för ärendetypen: ${regel.typ ?? regel}` });
    }
  }
  return hinder;
}

function harReferens(handelser, nycklar) {
  for (const h of av(handelser, "arbetsorder_skannad")) {
    for (const f of h.falt ?? []) {
      if (nycklar.includes(f.id) && String(f.varde ?? "").trim()) return true;
    }
  }
  return false;
}
