import { existsSync, readFileSync, readdirSync } from "node:fs";
import { dirname, join, normalize, resolve } from "node:path";
import ts from "typescript";
import {
  containsReadonlyPropMutation,
  inferredRuntimeType,
  parse,
  runtimeTypeOf,
  type PageAst,
  type RuntimeFunctionDecl,
  type ViewNode,
} from "@wrnexus/syntax";
import { componentContract, storeContract } from "./contracts.ts";
import { findAppRoot, loadApplicationTypes } from "./project.ts";
export { componentContract, storeContract } from "./contracts.ts";
export { findAppRoot, loadApplicationTypes } from "./project.ts";

export interface WrnTypeDiagnostic {
  code: string;
  category: "error" | "warning" | "info";
  message: string;
  file: string;
  line: number;
  column: number;
  length: number;
  expected?: string;
  received?: string;
  hint?: string;
  related?: { file: string; line: number; column: number; message: string };
}

export interface TypecheckOptions {
  filePath?: string;
  appRoot?: string;
  strict?: boolean;
  noImplicitAny?: boolean;
  strictNullChecks?: boolean;
  checkRuntimeBoundaries?: boolean;
}

interface SourceMapping {
  virtualStartLine: number;
  virtualEndLine: number;
  sourceStartLine: number;
  sourceStartColumn: number;
}

function resolveImportedWrn(source: string, filePath: string, appRoot: string): string | null {
  if (source.startsWith("@/")) return resolve(appRoot, "app", source.slice(2));
  if (source.startsWith(".")) return resolve(dirname(filePath), source);
  return null;
}

function workspacePackagePaths(appRoot: string): Record<string, string[]> {
  let current = resolve(appRoot);
  while (true) {
    const manifestPath = join(current, "package.json");
    if (existsSync(manifestPath)) {
      try {
        const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
        if (Array.isArray(manifest.workspaces) && existsSync(join(current, "packages"))) {
          const paths: Record<string, string[]> = {};
          for (const entry of readdirSync(join(current, "packages"), { withFileTypes: true })) {
            if (!entry.isDirectory()) continue;
            const packageRoot = join(current, "packages", entry.name);
            const packageManifestPath = join(packageRoot, "package.json");
            if (!existsSync(packageManifestPath)) continue;
            const packageManifest = JSON.parse(readFileSync(packageManifestPath, "utf8"));
            if (typeof packageManifest.name !== "string") continue;
            const main =
              typeof packageManifest.main === "string" ? packageManifest.main : "src/index.ts";
            paths[packageManifest.name] = [join(packageRoot, main)];
            paths[`${packageManifest.name}/*`] = [join(packageRoot, "src", "*.ts")];
          }
          return paths;
        }
      } catch {
        /* continue toward a parent workspace */
      }
    }
    const parent = dirname(current);
    if (parent === current) return {};
    current = parent;
  }
}

function importedWrnDeclarations(ast: PageAst, filePath: string, appRoot: string): string {
  const declarations: string[] = [];
  for (const entry of ast.structuredImports) {
    if (entry.typeOnly || !entry.source.endsWith(".wrn")) continue;
    const importedPath = resolveImportedWrn(entry.source, filePath, appRoot);
    if (!importedPath || !existsSync(importedPath)) continue;
    try {
      const importedAst = parse(readFileSync(importedPath, "utf8"));
      const isStore = importedAst.kind === "global-store" || importedAst.kind === "page-store";
      declarations.push(isStore ? storeContract(importedAst) : componentContract(importedAst));
      const typeName = isStore ? `${importedAst.name}Instance` : `${importedAst.name}Contract`;
      if (entry.defaultImport)
        declarations.push(`declare const ${entry.defaultImport}: ${typeName};`);
      if (entry.namespaceImport)
        declarations.push(`declare const ${entry.namespaceImport}: Record<string, unknown>;`);
      for (const item of entry.namedImports) {
        declarations.push(
          `declare const ${item.local}: ${item.imported === importedAst.name ? typeName : "unknown"};`,
        );
      }
    } catch {
      // The parser/import diagnostic layer reports malformed or unresolved .wrn imports.
    }
  }
  return declarations.join("\n\n");
}

export interface VirtualTypeScriptModule {
  ast: PageAst;
  fileName: string;
  code: string;
  mappings: SourceMapping[];
}

function lineAt(source: string, needle: string, occurrence = 0): { line: number; column: number } {
  let index = -1;
  let from = 0;
  for (let count = 0; count <= occurrence; count++) {
    index = source.indexOf(needle, from);
    if (index < 0) return { line: 1, column: 1 };
    from = index + Math.max(needle.length, 1);
  }
  const before = source.slice(0, index);
  const parts = before.split(/\r?\n/);
  return { line: parts.length, column: (parts.at(-1)?.length ?? 0) + 1 };
}

function runtimeNamespace(runtime: RuntimeFunctionDecl["runtime"]): string {
  return `__wrn_${runtime}`;
}

function functionDeclaration(fn: RuntimeFunctionDecl): string {
  const params = fn.parameters
    .map(
      (param) =>
        `${param.name}${param.optional ? "?" : ""}${param.valueType ? `: ${param.valueType}` : param.name === "event" ? ": CustomEvent<any> & { target: HTMLElement }" : ": any"}${param.default ? ` = ${param.default}` : ""}`,
    )
    .join(", ");
  // An omitted WRN parameter type follows JavaScript semantics. Emitting it as
  // explicit `any` prevents the virtual TypeScript document from inventing
  // false errors for valid dynamic handlers such as output[type](payload).
  // Developers can opt into strict checking by declaring the parameter type.
  return `export ${fn.async ? "async " : ""}function ${fn.name}(${params})${fn.returnType ? `: ${fn.returnType}` : ""} {${fn.body}}`;
}

function retainedImports(ast: PageAst): string {
  return ast.structuredImports
    .filter((entry) => {
      if (entry.source.endsWith(".wrn")) return false;
      if (entry.typeOnly) return true;
      return (
        !entry.source.startsWith("@/components/") &&
        !entry.source.startsWith("@/layouts/") &&
        !entry.source.startsWith("@/stores/")
      );
    })
    .map((entry) => entry.raw)
    .join("\n");
}

const RESERVED_BINDING_NAMES = new Set([
  "await",
  "break",
  "case",
  "catch",
  "class",
  "const",
  "continue",
  "debugger",
  "default",
  "delete",
  "do",
  "else",
  "enum",
  "export",
  "extends",
  "false",
  "finally",
  "for",
  "function",
  "if",
  "import",
  "in",
  "instanceof",
  "let",
  "new",
  "null",
  "return",
  "static",
  "super",
  "switch",
  "this",
  "throw",
  "true",
  "try",
  "typeof",
  "var",
  "void",
  "while",
  "with",
  "yield",
]);

function safeBindingName(name: string): boolean {
  return /^[A-Za-z_$][\w$]*$/.test(name) && !RESERVED_BINDING_NAMES.has(name);
}

export function virtualTypeScriptModule(
  source: string,
  filePath = "component.wrn",
  appRoot = findAppRoot(filePath),
): VirtualTypeScriptModule {
  const ast = parse(source);
  const chunks: string[] = [];
  const mappings: SourceMapping[] = [];
  let virtualLine = 1;

  const append = (code: string, sourceNeedle?: string, occurrence = 0): void => {
    if (!code) return;
    const lineCount = code.split(/\r?\n/).length;
    if (sourceNeedle) {
      const sourcePosition = lineAt(source, sourceNeedle, occurrence);
      mappings.push({
        virtualStartLine: virtualLine,
        virtualEndLine: virtualLine + lineCount - 1,
        sourceStartLine: sourcePosition.line,
        sourceStartColumn: sourcePosition.column,
      });
    }
    chunks.push(code);
    virtualLine += lineCount;
  };

  append(retainedImports(ast), ast.imports[0]);
  append(ast.types.join("\n\n"), ast.types[0]?.trim());
  // Pages, components, and stores are rendered with the framework request
  // context in scope. Mirror the compiler-generated function parameter here so
  // expressions such as `state user = ctx.user` type-check in the editor.
  append('declare const ctx: import("@wrnexus/core").Context;');

  for (const prop of ast.props) {
    // JavaScript keywords such as the conventional `class` component prop are
    // valid WRN prop names but cannot be emitted as standalone TS bindings.
    // They remain available through the typed `props` object.
    if (!safeBindingName(prop.name)) continue;
    append(`declare const ${prop.name}: Readonly<${prop.valueType ?? "unknown"}>;`, prop.name);
  }
  for (const state of ast.states) {
    append(
      `let ${state.name}${state.valueType ? `: ${state.valueType}` : ""} = (${state.expr});`,
      state.name,
    );
  }
  for (const computed of ast.computed) {
    append(
      `const ${computed.name}${computed.valueType ? `: ${computed.valueType}` : ""} = (${computed.expr});`,
      computed.name,
    );
  }

  const outputType = ast.outputs
    .map(
      (output) =>
        `${output.name}: (${output.payload ? `${output.payload.name}${output.payload.optional ? "?" : ""}: ${output.payload.valueType}` : ""}) => void`,
    )
    .join("; ");
  const serverFunctions = ast.runtimeFunctions.filter((fn) => fn.runtime === "server");
  const serverType = serverFunctions
    .map(
      (fn) =>
        `${fn.name}: (${fn.parameters.map((param) => `${param.name}${param.optional ? "?" : ""}: ${param.valueType ?? "unknown"}`).join(", ")}) => ${fn.returnType ?? (fn.async ? "Promise<unknown>" : "unknown")}`,
    )
    .join("; ");
  append(`declare const output: { [name: string]: (...args: any[]) => void; ${outputType} };`);
  for (const output of ast.outputs)
    append(
      `declare const ${output.name}: (${output.payload ? `${output.payload.name}${output.payload.optional ? "?" : ""}: ${output.payload.valueType}` : ""}) => void;`,
    );
  for (const match of source.matchAll(/@event\s+([A-Za-z_$][\w$]*)\s*=\s*function/g))
    append(`declare const ${match[1]}: ((...args: unknown[]) => void) | undefined;`);
  append(`declare const server: { ${serverType} };`);
  append(`declare const props: Readonly<${ast.name}Props>;`);
  append("declare const refs: Record<string, Element | null>;");
  append(
    ast.kind === "global-store" || ast.kind === "page-store"
      ? storeContract(ast)
      : componentContract(ast),
  );
  append(importedWrnDeclarations(ast, filePath, appRoot));

  const sharedNames = new Set(
    ast.runtimeFunctions.filter((fn) => fn.runtime === "shared").map((fn) => fn.name),
  );
  for (const runtime of ["shared", "client", "server", "legacy"] as const) {
    const functions = ast.runtimeFunctions.filter((fn) => fn.runtime === runtime);
    if (!functions.length) continue;
    const localNames = new Set(functions.map((fn) => fn.name));
    const sharedAliases =
      runtime !== "shared"
        ? [...sharedNames]
            .filter((name) => !localNames.has(name))
            .map((name) => `const ${name} = ${runtimeNamespace("shared")}.${name};`)
            .join("\n")
        : "";
    append(`namespace ${runtimeNamespace(runtime)} {\n${sharedAliases}`);
    for (const fn of functions) append(functionDeclaration(fn), fn.source);
    append("}");
  }

  return {
    ast,
    fileName: filePath.replace(/\.wrn$/i, ".wrn.ts"),
    code: chunks.join("\n") + "\n",
    mappings,
  };
}

interface ComponentShape {
  name: string;
  props: Array<{ name: string; type: string; required: boolean; options?: string[] }>;
  outputs: Array<{ name: string; payloadType?: string }>;
}

function shapeFromAst(ast: PageAst): ComponentShape {
  return {
    name: ast.name,
    props: ast.props.map((prop) => ({
      name: prop.name,
      type: prop.valueType ?? "unknown",
      required: prop.required,
      options: /^\s*(?:"[^"]+"\s*\|\s*)+"[^"]+"\s*$/.test(prop.valueType ?? "")
        ? (prop.valueType ?? "").split("|").map((part) => part.trim().replace(/^"|"$/g, ""))
        : undefined,
    })),
    outputs: ast.outputs.map((output) => ({
      name: output.name,
      payloadType: output.payload?.valueType,
    })),
  };
}

function loadUiShapes(appRoot: string): Map<string, ComponentShape> {
  const candidates = [
    join(appRoot, "node_modules", "@wrnexus", "ui", "component-reference.json"),
    resolve(process.cwd(), "packages", "ui", "component-reference.json"),
  ];
  for (const candidate of candidates) {
    if (!existsSync(candidate)) continue;
    try {
      const parsed = JSON.parse(readFileSync(candidate, "utf8")) as {
        components?: Array<{
          name: string;
          props?: Array<{ name: string; type?: string; required?: boolean; options?: string[] }>;
          outputs?: Array<{ name: string; payloadType?: string }>;
        }>;
      };
      return new Map(
        (parsed.components ?? []).map((component) => [
          component.name,
          {
            name: component.name,
            props: (component.props ?? []).map((prop) => ({
              name: prop.name,
              type: prop.type ?? "unknown",
              required: Boolean(prop.required),
              options: prop.options,
            })),
            outputs: component.outputs ?? [],
          },
        ]),
      );
    } catch {
      // Ignore a stale or malformed optional reference; import diagnostics report it separately.
    }
  }
  return new Map();
}

function importedComponentShapes(
  ast: PageAst,
  filePath: string,
  appRoot: string,
): Map<string, ComponentShape> {
  const shapes = new Map<string, ComponentShape>();
  const ui = loadUiShapes(appRoot);
  for (const entry of ast.structuredImports) {
    if (entry.typeOnly) continue;
    if (entry.source === "@wrnexus/ui") {
      for (const item of entry.namedImports) {
        const shape = ui.get(item.imported);
        if (shape) shapes.set(item.local, shape);
      }
      continue;
    }
    if (!entry.source.endsWith(".wrn")) continue;
    const importedPath = resolveImportedWrn(entry.source, filePath, appRoot);
    if (!importedPath || !existsSync(importedPath)) continue;
    try {
      const importedAst = parse(readFileSync(importedPath, "utf8"));
      if (importedAst.kind !== "component") continue;
      const shape = shapeFromAst(importedAst);
      if (entry.defaultImport) shapes.set(entry.defaultImport, shape);
      for (const item of entry.namedImports)
        if (item.imported === importedAst.name) shapes.set(item.local, shape);
    } catch {
      // Syntax/import diagnostics are emitted elsewhere.
    }
  }
  return shapes;
}

function walkView(
  nodes: ViewNode[],
  visit: (node: Extract<ViewNode, { type: "element" }>) => void,
): void {
  for (const node of nodes) {
    if (node.type === "element") {
      visit(node);
      walkView(node.children, visit);
    } else if (node.type === "each") {
      walkView(node.body, visit);
      walkView(node.empty, visit);
    } else if (node.type === "if") {
      for (const branch of node.branches) walkView(branch.body, visit);
    }
  }
}

function componentUsageDiagnostics(
  source: string,
  ast: PageAst,
  filePath: string,
  appRoot: string,
): WrnTypeDiagnostic[] {
  const shapes = importedComponentShapes(ast, filePath, appRoot);
  const diagnostics: WrnTypeDiagnostic[] = [];
  walkView(ast.view, (node) => {
    const shape = shapes.get(node.tag);
    if (!shape) return;
    const attributes = new Map(
      node.attrs.filter((attr) => !attr.event).map((attr) => [attr.name, attr]),
    );
    const outputNames = new Set(shape.outputs.map((output) => output.name));
    const position = lineAt(source, `<${node.tag}`);
    for (const prop of shape.props) {
      if (prop.required && !attributes.has(prop.name)) {
        diagnostics.push({
          code: "WRN-COMPONENT-MISSING-PROP",
          category: "error",
          message: `<${node.tag}> is missing required prop '${prop.name}'.`,
          file: filePath,
          line: position.line,
          column: position.column,
          length: node.tag.length + 1,
          expected: prop.type,
          hint: `Add ${prop.name} with a value assignable to ${prop.type}.`,
        });
      }
    }
    const known = new Map(shape.props.map((prop) => [prop.name, prop]));
    for (const attr of node.attrs) {
      if (attr.event) {
        if (!outputNames.has(attr.name)) {
          diagnostics.push({
            code: "WRN-OUTPUT-UNKNOWN-HANDLER",
            category: "error",
            message: `<${node.tag}> does not declare output '${attr.name}'.`,
            file: filePath,
            line: position.line,
            column: position.column,
            length: attr.name.length,
            hint: "Use an output declared by the component contract.",
          });
        }
        continue;
      }
      if (/^(?:class|id|style|slot|data-|aria-)/.test(attr.name) || attr.name === "attrs") continue;
      const prop = known.get(attr.name);
      if (!prop) {
        diagnostics.push({
          code: "WRN-COMPONENT-UNKNOWN-PROP",
          category: "error",
          message: `<${node.tag}> has unknown prop '${attr.name}'.`,
          file: filePath,
          line: position.line,
          column: position.column,
          length: attr.name.length,
          hint: "Remove the prop or add it to the component declaration.",
        });
        continue;
      }
      if (attr.value.startsWith("{") && attr.value.endsWith("}")) continue;
      const received = attr.boolean ? "boolean" : inferredRuntimeType(JSON.stringify(attr.value));
      const expected = runtimeTypeOf(prop.type);
      const compatibleLiteral =
        (expected === "boolean" && /^(?:true|false|1|0|yes|no|on|off)?$/i.test(attr.value)) ||
        (expected === "number" && attr.value.trim() !== "" && Number.isFinite(Number(attr.value)));
      if (
        expected !== "unknown" &&
        received !== "unknown" &&
        expected !== received &&
        !compatibleLiteral
      ) {
        diagnostics.push({
          code: "WRN-COMPONENT-PROP-TYPE",
          category: "error",
          message: `Prop '${attr.name}' on <${node.tag}> expects ${prop.type}, received ${received}.`,
          file: filePath,
          line: position.line,
          column: position.column,
          length: attr.name.length,
          expected: prop.type,
          received,
        });
      }
      if (prop.options?.length && !prop.options.includes(attr.value)) {
        diagnostics.push({
          code: "WRN-COMPONENT-PROP-LITERAL",
          category: "error",
          message: `Prop '${attr.name}' on <${node.tag}> must be one of ${prop.options.map((value) => JSON.stringify(value)).join(", ")}.`,
          file: filePath,
          line: position.line,
          column: position.column,
          length: attr.name.length,
          expected: prop.type,
          received: JSON.stringify(attr.value),
        });
      }
    }
  });
  return diagnostics;
}

function category(value: ts.DiagnosticCategory): WrnTypeDiagnostic["category"] {
  return value === ts.DiagnosticCategory.Error
    ? "error"
    : value === ts.DiagnosticCategory.Warning
      ? "warning"
      : "info";
}

function hostWithVirtualFiles(
  files: Map<string, string>,
  options: ts.CompilerOptions,
  standardLibraryDirectory?: string,
): ts.CompilerHost {
  const host = ts.createCompilerHost(options, true);
  if (standardLibraryDirectory) {
    host.getDefaultLibFileName = (compilerOptions) =>
      join(standardLibraryDirectory, ts.getDefaultLibFileName(compilerOptions));
    (host as ts.CompilerHost & { getDefaultLibLocation?: () => string }).getDefaultLibLocation =
      () => standardLibraryDirectory;
  }
  const originalGet = host.getSourceFile.bind(host);
  host.fileExists = (fileName) => files.has(normalize(fileName)) || ts.sys.fileExists(fileName);
  host.readFile = (fileName) => files.get(normalize(fileName)) ?? ts.sys.readFile(fileName);
  host.getSourceFile = (fileName, languageVersion, onError, shouldCreateNewSourceFile) => {
    const text = files.get(normalize(fileName));
    return text === undefined
      ? originalGet(fileName, languageVersion, onError, shouldCreateNewSourceFile)
      : ts.createSourceFile(fileName, text, languageVersion, true, ts.ScriptKind.TS);
  };
  return host;
}

function resolveStandardLibraryDirectory(
  appRoot: string,
  options: ts.CompilerOptions,
): string | null {
  const candidates = [
    dirname(ts.getDefaultLibFilePath(options)),
    join(appRoot, "node_modules", "typescript", "lib"),
    join(process.cwd(), "node_modules", "typescript", "lib"),
  ];
  for (const candidate of candidates) {
    if ((options.lib ?? []).every((name) => ts.sys.fileExists(join(candidate, name)))) {
      return candidate;
    }
  }
  return null;
}

function mappedPosition(
  virtual: VirtualTypeScriptModule,
  line: number,
  column: number,
): { line: number; column: number } | null {
  const mapping = virtual.mappings.find(
    (entry) => line >= entry.virtualStartLine && line <= entry.virtualEndLine,
  );
  if (!mapping) return null;
  const offset = line - mapping.virtualStartLine;
  return {
    line: mapping.sourceStartLine + offset,
    column: offset === 0 ? mapping.sourceStartColumn + Math.max(column - 1, 0) : column,
  };
}

function runtimeDiagnostics(source: string, ast: PageAst, filePath: string): WrnTypeDiagnostic[] {
  const diagnostics: WrnTypeDiagnostic[] = [];
  const duplicateKeys = new Map<string, RuntimeFunctionDecl>();
  for (const fn of ast.runtimeFunctions) {
    const key = `${fn.runtime}:${fn.name}`;
    const previous = duplicateKeys.get(key);
    if (previous) {
      const position = lineAt(source, fn.source);
      const related = lineAt(source, previous.source);
      diagnostics.push({
        code: "WRN-FUNCTION-DUPLICATE",
        category: "error",
        message: `Duplicate ${fn.runtime} implementation for '${fn.name}'.`,
        file: filePath,
        line: position.line,
        column: position.column,
        length: fn.name.length,
        hint: "Keep only one implementation for each function name and runtime.",
        related: {
          file: filePath,
          line: related.line,
          column: related.column,
          message: "First implementation is here.",
        },
      });
    } else duplicateKeys.set(key, fn);

    const position = lineAt(source, fn.source);
    const parameterNames = new Set(fn.parameters.map((parameter) => parameter.name));
    for (const prop of ast.props) {
      if (containsReadonlyPropMutation(fn.body, prop.name, parameterNames)) {
        diagnostics.push({
          code: "WRN-PROP-READONLY",
          category: "error",
          message: `Function '${fn.name}' attempts to mutate readonly prop '${prop.name}'.`,
          file: filePath,
          line: position.line,
          column: position.column,
          length: fn.name.length,
          hint: "Copy the prop into state or call a component output instead.",
        });
      }
    }
    if (fn.runtime === "server" && /\boutput\s*\./.test(fn.body)) {
      diagnostics.push({
        code: "WRN-OUTPUT-SERVER-CALL",
        category: "error",
        message: `Server function '${fn.name}' cannot call a component output.`,
        file: filePath,
        line: position.line,
        column: position.column,
        length: fn.name.length,
        hint: "Return a typed value to the client function and call output.name(payload) there.",
      });
    }
    if (
      fn.runtime === "client" &&
      /\b(?:Bun|process|Deno|ctx\.(?:db|request|req))\b/.test(fn.body)
    ) {
      diagnostics.push({
        code: "WRN-CLIENT-SERVER-API",
        category: "error",
        message: `Client function '${fn.name}' references a server-only API.`,
        file: filePath,
        line: position.line,
        column: position.column,
        length: fn.name.length,
        hint: "Move server work to a server function and call it through server.name(...).",
      });
    }
    if (
      fn.runtime === "server" &&
      /\b(?:window|document|localStorage|sessionStorage|navigator)\b/.test(fn.body)
    ) {
      diagnostics.push({
        code: "WRN-SERVER-BROWSER-API",
        category: "error",
        message: `Server function '${fn.name}' references a browser-only API.`,
        file: filePath,
        line: position.line,
        column: position.column,
        length: fn.name.length,
        hint: "Move browser work to a client function.",
      });
    }
  }
  return diagnostics;
}

export function checkWrnSource(
  source: string,
  options: TypecheckOptions = {},
): WrnTypeDiagnostic[] {
  const filePath = resolve(options.filePath ?? "component.wrn");
  const appRoot = options.appRoot ?? findAppRoot(filePath);
  let virtual: VirtualTypeScriptModule;
  try {
    virtual = virtualTypeScriptModule(source, filePath, appRoot);
  } catch (error) {
    return [
      {
        code: "WRN-TYPE-PARSE",
        category: "error",
        message: error instanceof Error ? error.message : String(error),
        file: filePath,
        line: 1,
        column: 1,
        length: 1,
      },
    ];
  }

  const appTypes = loadApplicationTypes(appRoot);
  const files = new Map<string, string>();
  files.set(normalize(virtual.fileName), virtual.code);
  for (const [name, text] of appTypes.files) files.set(normalize(name), text);

  const compilerOptions: ts.CompilerOptions = {
    target: ts.ScriptTarget.ESNext,
    module: ts.ModuleKind.ESNext,
    moduleResolution: ts.ModuleResolutionKind.Bundler,
    strict: options.strict ?? true,
    noImplicitAny: options.noImplicitAny ?? true,
    strictNullChecks: options.strictNullChecks ?? true,
    skipLibCheck: true,
    allowImportingTsExtensions: true,
    allowArbitraryExtensions: true,
    noEmit: true,
    baseUrl: appRoot,
    paths: { "@/*": ["app/*"], ...workspacePackagePaths(appRoot) },
    lib: ["lib.esnext.d.ts", "lib.dom.d.ts", "lib.dom.iterable.d.ts"],
  };
  const rootNames = [virtual.fileName, ...appTypes.files.keys()];
  const standardLibraryDirectory = resolveStandardLibraryDirectory(appRoot, compilerOptions);

  const tsDiagnostics: WrnTypeDiagnostic[] = [];
  if (standardLibraryDirectory) {
    const program = ts.createProgram(
      rootNames,
      compilerOptions,
      hostWithVirtualFiles(files, compilerOptions, standardLibraryDirectory),
    );
    for (const diagnostic of ts.getPreEmitDiagnostics(program)) {
      const file = diagnostic.file;
      if (!file) continue;
      const normalizedFile = normalize(file.fileName);
      const isVirtual = normalizedFile === normalize(virtual.fileName);
      const isApplicationType = files.has(normalizedFile);
      if (!isVirtual && !isApplicationType) continue;

      const start = diagnostic.start ?? 0;
      const virtualPosition = file.getLineAndCharacterOfPosition(start);
      const sourcePosition = isVirtual
        ? mappedPosition(virtual, virtualPosition.line + 1, virtualPosition.character + 1)
        : { line: virtualPosition.line + 1, column: virtualPosition.character + 1 };
      // Synthetic declarations without a source mapping are implementation
      // details of the virtual document and must not appear as editor errors.
      if (!sourcePosition) continue;
      tsDiagnostics.push({
        code: `WRN-TYPE-${diagnostic.code}`,
        category: category(diagnostic.category),
        message: ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n"),
        file: isVirtual ? filePath : file.fileName,
        line: sourcePosition.line,
        column: sourcePosition.column,
        length: diagnostic.length ?? 1,
        hint: "Fix the TypeScript contract or expression in the related .wrn declaration.",
      });
    }
  }
  return [
    ...(options.checkRuntimeBoundaries === false
      ? []
      : runtimeDiagnostics(source, virtual.ast, filePath)),
    ...componentUsageDiagnostics(source, virtual.ast, filePath, appRoot),
    ...tsDiagnostics,
  ];
}

export function checkWrnFile(
  filePath: string,
  options: Omit<TypecheckOptions, "filePath"> = {},
): WrnTypeDiagnostic[] {
  return checkWrnSource(readFileSync(filePath, "utf8"), { ...options, filePath });
}
