import { describe, test } from "node:test";
import assert from "node:assert/strict";
import { mkdtempSync, writeFileSync, rmSync } from "node:fs";
import { join } from "node:path";
import { tmpdir } from "node:os";

import { evaluate, runGateFile, mask } from "../src/gate.js";
import { RULES } from "../src/rules.js";

const ids = (vs: { ruleId: string }[]) => vs.map((v) => v.ruleId);

describe("contract-gate engine", () => {
  describe("clean draft", () => {
    test("a contract-compliant snippet passes with no blocks and no warns", () => {
      const text =
        "Jeg bruker KI hver dag og ser hvordan saksbehandlere henter ut potensialet.\n" +
        "En kontraktsjurist sparte to dager. Verktøyet leverer det vi ber om, og " +
        "ledelsen tar valget.";
      const r = evaluate(text, RULES);
      assert.equal(r.passed, true);
      assert.equal(r.blocks.length, 0, `unexpected blocks: ${ids(r.blocks)}`);
      assert.equal(r.warns.length, 0, `unexpected warns: ${ids(r.warns)}`);
    });
  });

  describe("BLOCK: modell-stråmann (§B-3)", () => {
    test("an exact forbidden phrase blocks handoff", () => {
      const text = "Poenget er at det er ikke modellen som mangler, men kompetansen.";
      const r = evaluate(text, RULES);
      assert.equal(r.passed, false);
      assert.ok(ids(r.blocks).includes("B3-modell-referansepunkt"));
    });

    test("'modellen er god nok' blocks", () => {
      const r = evaluate("Mange tror modellen er god nok allerede.", RULES);
      assert.equal(r.passed, false);
      assert.ok(ids(r.blocks).includes("B3-modell-referansepunkt"));
    });
  });

  describe("BLOCK: serie-tese (§B-10)", () => {
    test("the meta-thesis 'er ikke modellen' blocks", () => {
      const text = "Tesen er enkel: den knappe ressursen er ikke modellen.";
      const r = evaluate(text, RULES);
      assert.equal(r.passed, false);
      assert.ok(ids(r.blocks).includes("B10-serie-tese"));
    });

    test("REGRESSION: positive concrete landing 'er den knappe ressursen' does NOT block", () => {
      const text =
        "Menneskene i organisasjonen er den knappe ressursen, ikke verktøyet.";
      const r = evaluate(text, RULES);
      assert.equal(r.passed, true, `unexpected blocks: ${ids(r.blocks)}`);
      assert.ok(!ids(r.blocks).includes("B10-serie-tese"));
    });
  });

  describe("BLOCK: _understrek_ emphasis (§C1)", () => {
    test("underscore emphasis blocks", () => {
      const r = evaluate("Dette er _viktig_ å forstå.", RULES);
      assert.equal(r.passed, false);
      assert.ok(ids(r.blocks).includes("understrek-emfase"));
    });

    test("snake_case identifiers in prose do NOT block", () => {
      const r = evaluate("Variabelen LINKEDIN_STUDIO_DATA styrer roten.", RULES);
      assert.ok(!ids(r.blocks).includes("understrek-emfase"));
    });
  });

  describe("code masking", () => {
    test("a forbidden phrase inside a fenced code block is NOT flagged", () => {
      const text =
        "Her er konfigen:\n```\nmodellen er god nok\n```\nEllers ren prosa.";
      const r = evaluate(text, RULES);
      assert.equal(r.passed, true, `unexpected blocks: ${ids(r.blocks)}`);
    });

    test("a forbidden phrase inside an inline code span is NOT flagged", () => {
      const r = evaluate("Flagget `er ikke modellen` i koden.", RULES);
      assert.ok(!ids(r.blocks).includes("B10-serie-tese"));
    });

    test("mask preserves line count and positions", () => {
      const text = "linje1\n```\nkode\n```\nlinje5";
      const masked = mask(text);
      assert.equal(masked.split("\n").length, text.split("\n").length);
      assert.equal(masked.length, text.length);
    });
  });

  describe("WARN: non-blocking flags", () => {
    test("a metaphor word warns but does not block", () => {
      const text = "Det er et usynlig tak over hele organisasjonen.";
      const r = evaluate(text, RULES);
      assert.equal(r.passed, true);
      assert.ok(ids(r.warns).includes("B2-metafor"));
      assert.ok(!ids(r.blocks).includes("B2-metafor"));
    });

    test("high em-dash density warns", () => {
      const text = "Ord — ord — ord — ord — ord.";
      const r = evaluate(text, RULES);
      assert.ok(ids(r.warns).includes("C2-tankestrek-tetthet"));
    });

    test("uppercase emphasis word warns", () => {
      const dirty = evaluate("Dette er IKKE bra, selv om KI og IT er OK.", RULES);
      assert.ok(ids(dirty.warns).includes("C2-versal-tic"));
    });

    test("REGRESSION: acronyms (NPM/DFØ/NAV) are NOT versal-tics", () => {
      // Denylist semantics: only UPPERCASE emphasis words flag, never acronyms.
      const r = evaluate("Verktøyet NPM og CLI, og DFØ og NAV, er nyttige.", RULES);
      assert.ok(!ids(r.warns).includes("C2-versal-tic"), `flagged: ${ids(r.warns)}`);
    });

    test("uppercase 'MÅ' (with Å) is caught by the Unicode-aware pattern", () => {
      const r = evaluate("Du MÅ gjøre dette nå.", RULES);
      assert.ok(ids(r.warns).includes("C2-versal-tic"));
    });

    test("a plain acronym sentence raises no versal warn", () => {
      const clean = evaluate("KI og IT samarbeider tett med NSM og EU.", RULES);
      assert.ok(!ids(clean.warns).includes("C2-versal-tic"));
    });

    test("'KI-en' as subject warns only past the repetition threshold", () => {
      const few = evaluate("KI-en gjør jobben.", RULES);
      assert.ok(!ids(few.warns).includes("KI-en-subjekt"));

      const many = evaluate(
        "KI-en gjør X. KI-en gjør Y. KI-en gjør Z. KI-en gjør W.",
        RULES,
      );
      assert.ok(ids(many.warns).includes("KI-en-subjekt"));
    });

    test("bare 'modell' is surfaced as a warn for per-occurrence review", () => {
      const r = evaluate("En KI-modell er trent på data.", RULES);
      assert.ok(ids(r.warns).includes("B3-modell-bar"));
      assert.equal(r.passed, true);
    });
  });

  describe("reporting", () => {
    test("line numbers point at the original draft line", () => {
      const text = "linje1\nlinje2\nDette er _viktig_ her.\nlinje4";
      const r = evaluate(text, RULES);
      const v = r.blocks.find((b) => b.ruleId === "understrek-emfase");
      assert.ok(v);
      assert.equal(v!.matches[0].line, 3);
    });

    test("count rules report observed and threshold", () => {
      const r = evaluate("En modell, to modeller, tre modell-typer.", RULES);
      const v = r.warns.find((w) => w.ruleId === "B3-modell-bar");
      assert.ok(v);
      assert.ok((v!.observed ?? 0) >= 3);
    });
  });

  describe("runGateFile", () => {
    test("reads a draft file and evaluates it", () => {
      const dir = mkdtempSync(join(tmpdir(), "contract-gate-"));
      try {
        const p = join(dir, "draft.md");
        writeFileSync(p, "Tesen: den knappe ressursen er ikke modellen.");
        const r = runGateFile(p);
        assert.equal(r.draftPath, p);
        assert.equal(r.passed, false);
        assert.ok(ids(r.blocks).includes("B10-serie-tese"));
      } finally {
        rmSync(dir, { recursive: true, force: true });
      }
    });
  });
});
