"use strict";

const assert = require("node:assert");
const { test } = require("node:test");
const Module = require("node:module");

const originalLoad = Module._load;
Module._load = function load(request, parent, isMain) {
  if (request === "vscode") {
    return {
      Diagnostic: class Diagnostic {
        constructor(range, message, severity) {
          this.range = range;
          this.message = message;
          this.severity = severity;
        }
      },
      DiagnosticSeverity: { Error: 0, Warning: 1 },
      Range: class Range {
        constructor(start, end) {
          this.start = start;
          this.end = end;
        }
      },
    };
  }
  return originalLoad.call(this, request, parent, isMain);
};
const {
  findTopLevelDeclaration,
  maskLeadingTrivia,
  validateBalancedCharacters,
  validateHtmlTags,
  validateLayoutUsage,
  validateRootMembers,
} = require("../src/diagnostics");
Module._load = originalLoad;

function mockDocument() {
  return {
    positionAt(offset) {
      return offset;
    },
  };
}

test("recognizes a WRN declaration after leading line comments", () => {
  const source = `// Component summary
// More details

component ThemeToggle {
  view { <button>Toggle</button> }
}`;

  const declaration = findTopLevelDeclaration({}, source);

  assert.equal(declaration.kind, "component");
  assert.equal(declaration.name, "ThemeToggle");
  assert.equal(declaration.diagnostic, undefined);
});

test("recognizes a WRN declaration after top-level imports", () => {
  const source = `import { appUrl } from "@wrnexus/helpers";
import type { Context } from "@wrnexus/core";

page Home {
  view { <PublicHeader signInHref="{appUrl('sso', '/sign-in')}" /> }
}`;
  const declaration = findTopLevelDeclaration({}, source);
  assert.equal(declaration.kind, "page");
  assert.equal(declaration.name, "Home");
  assert.equal(declaration.diagnostic, undefined);
});

test("masks only leading trivia and preserves source offsets", () => {
  const source = "\uFEFF// Summary\r\npage Home {\n  // member comment\n}";
  const masked = maskLeadingTrivia(source);

  assert.equal(masked.length, source.length);
  assert.equal(masked.indexOf("page Home"), source.indexOf("page Home"));
  assert.match(masked, /\/\/ member comment/);
});

test("ignores apostrophes and brackets in line comments when checking balance", () => {
  const source = `// The framework's theme runtime binds the click }
component ThemeToggle {
  props {
    label = "Toggle theme"
    class = ""
  }
  view { <button class="{class}" data-wire-theme-toggle><slot>{label}</slot></button> }
}`;

  const diagnostics = validateBalancedCharacters(mockDocument(source), source);

  assert.deepEqual(diagnostics, []);
});

test("ignores apostrophes in rendered HTML copy when checking balance", () => {
  const source = `import LanguageSwitcher from "@wrnexus/i18n/components/LanguageSwitcher.wrn"

page InternationalizationShowcase {
  view {
    <main>
      <p>Parameters such as a person's name are inserted safely.</p>
      <LanguageSwitcher />
    </main>
  }
}`;

  assert.deepEqual(validateBalancedCharacters(mockDocument(source), source), []);
});

test("accepts all WRN 0.3 root members without false unknown-member errors", () => {
  const source = `page Dashboard {
    runtime = "universal"
    hydrate = "visible"
    computed { doubled = count * 2 }
    effect { console.log(doubled) }
    security { auth = "required" }
    load server { return {} }
    action save(input) { return input }
    client { functions { function ready() {} } }
    view { <main>{doubled}</main> }
  }`;
  const declaration = findTopLevelDeclaration({}, source);
  const document = {
    positionAt(offset) {
      return offset;
    },
  };

  assert.deepEqual(validateRootMembers(document, source, declaration.kind, declaration.match), []);
});

test("ignores member-like words inside component line and block comments", () => {
  const source = `component Counter {
    // Props are passed as attributes on the mount element.
    /* State and View are explained here, not declared here. */
    props {
      start = 0
      label = "Count"
    }
    state count = start
    view { <button>{label}: {count}</button> }
  }`;
  const declaration = findTopLevelDeclaration({}, source);

  assert.deepEqual(
    validateRootMembers(mockDocument(source), source, declaration.kind, declaration.match),
    [],
  );
});

test("allows a component prop named layout but rejects a root layout member", () => {
  const component = `component Card {
    props {
      layout = "vertical"
    }
    view { <article>{layout}</article> }
  }`;
  const componentDeclaration = findTopLevelDeclaration({}, component);

  assert.deepEqual(
    validateLayoutUsage(
      mockDocument(component),
      component,
      componentDeclaration.kind,
      componentDeclaration.match,
    ),
    [],
  );

  const invalid = `component Card {
    layout = "dashboard"
    view { <article></article> }
  }`;
  const invalidDeclaration = findTopLevelDeclaration({}, invalid);
  const diagnostics = validateLayoutUsage(
    mockDocument(invalid),
    invalid,
    invalidDeclaration.kind,
    invalidDeclaration.match,
  );

  assert.equal(diagnostics.length, 1);
  assert.equal(diagnostics[0].code, "wrn-invalid-layout-member");
});

test("ignores HTML-like tags inside WRN comments", () => {
  const source = `// The <section> below listens for child events.
// <ComboBox> is only documentation in this comment.
page Test {
  /*
    Example markup: <article><strong>Preview</strong></article>
  */
  view {
    <main><section>Real content</section></main>
  }
}`;

  assert.deepEqual(validateHtmlTags(mockDocument(source), source), []);
});

test("does not interpret TypeScript generic types in outputs as HTML tags", () => {
  const source = `component AuthForm {
  outputs {
    change(payload: { values?: Array<string | number | boolean | null | object>; sourceEvent?: Event })
  }
  view { <form><strong>Sign in</strong></form> }
}`;

  assert.deepEqual(validateHtmlTags(mockDocument(source), source), []);
});

test("allows JavaScript-looking documentation text inside pre and code", () => {
  const source = `page Docs {
  view {
    <pre><code><span class="code-muted">// One project, one language</span>
<span class="code-key">page</span> Dashboard {
  &lt;button&gt;Ship&lt;/button&gt;
}</code></pre>
  }
}`;

  assert.deepEqual(validateHtmlTags(mockDocument(source), source), []);
});

test("still reports genuinely mismatched view tags", () => {
  const source = `page Broken {
  view { <main><strong>Broken</main> }
}`;
  const diagnostics = validateHtmlTags(mockDocument(source), source);
  assert.equal(diagnostics.length, 1);
  assert.equal(diagnostics[0].code, "wrn-mismatched-html-tag");
});
