// @ts-check
"use strict";

const vscode = require("vscode");

const { formatWrn } = require("./formatter");

const { registerCompletionProvider } = require("./completion");

const { registerDefinitionProvider } = require("./definition");

const { registerDiagnostics } = require("./diagnostics");

/**
 * The WRN compiler is bundled to CommonJS using:
 *
 *   bun run build:compiler
 *
 * Loading is optional so highlighting, formatting, snippets,
 * autocomplete and navigation continue working when the compiler
 * bundle is temporarily unavailable.
 */
let compiler = null;

try {
  compiler = require("./compiler.cjs");
} catch (error) {
  console.warn(
    "[wrnexus] compiler bundle not found; compiler diagnostics disabled.",
    error instanceof Error ? error.message : String(error),
  );
}

const WRN_LANGUAGE_ID = "wrn";
const COMPILER_DIAGNOSTIC_COLLECTION = "wrnexus-compiler";

/**
 * Register the WRN document formatter.
 *
 * @param {vscode.ExtensionContext} context
 */
function registerFormatter(context) {
  const provider = vscode.languages.registerDocumentFormattingEditProvider(
    {
      language: WRN_LANGUAGE_ID,
      scheme: "file",
    },
    {
      provideDocumentFormattingEdits(document, options) {
        const configuration = vscode.workspace.getConfiguration("wrnexus", document.uri);

        const enabled = configuration.get("format.enable", true);

        if (!enabled) {
          return [];
        }

        const source = document.getText();

        const printWidth = configuration.get("formatting.printWidth", 100);

        const formatted = formatWrn(source, {
          tabSize: options.tabSize,
          insertSpaces: options.insertSpaces,
          printWidth,
        });

        if (formatted === source) {
          return [];
        }

        const range = new vscode.Range(document.positionAt(0), document.positionAt(source.length));

        return [vscode.TextEdit.replace(range, formatted)];
      },
    },
  );

  context.subscriptions.push(provider);
}

/**
 * Convert a compiler ParseError into a VS Code diagnostic.
 *
 * Compiler errors normally contain:
 *
 *   at offset 123
 *
 * When no offset exists, the diagnostic is attached to the last
 * line of the document.
 *
 * @param {vscode.TextDocument} document
 * @param {unknown} error
 * @returns {vscode.Diagnostic}
 */
function toCompilerDiagnostic(document, error) {
  const message =
    error && typeof error === "object" && "message" in error
      ? String(error.message)
      : "Failed to compile .wrn file.";

  const offsetMatch = /offset\s+(\d+)/i.exec(message);

  let range;

  if (offsetMatch) {
    const requestedOffset = Number(offsetMatch[1]);

    const safeOffset = Math.max(0, Math.min(requestedOffset, document.getText().length));

    const start = document.positionAt(safeOffset);

    const wordRange = document.getWordRangeAtPosition(start);

    range =
      wordRange ||
      new vscode.Range(
        start,
        document.positionAt(Math.min(safeOffset + 1, document.getText().length)),
      );
  } else {
    const lastLine = document.lineAt(Math.max(0, document.lineCount - 1));

    range = lastLine.range;
  }

  const diagnostic = new vscode.Diagnostic(range, message, vscode.DiagnosticSeverity.Error);

  diagnostic.source = "WRNexus Compiler";

  diagnostic.code = "wrn-compiler-error";

  return diagnostic;
}

/**
 * Register diagnostics produced by the actual WRN compiler.
 *
 * The lightweight diagnostics in diagnostics.js provide immediate
 * editor feedback. Compiler diagnostics verify that the document
 * can also be parsed and generated by the real framework compiler.
 *
 * @param {vscode.ExtensionContext} context
 */
function registerCompilerDiagnostics(context) {
  const collection = vscode.languages.createDiagnosticCollection(COMPILER_DIAGNOSTIC_COLLECTION);

  const timers = new Map();

  /**
   * @param {vscode.TextDocument} document
   */
  const run = (document) => {
    if (document.languageId !== WRN_LANGUAGE_ID) {
      return;
    }

    const configuration = vscode.workspace.getConfiguration("wrnexus", document.uri);

    const enabled = configuration.get("diagnostics.enable", true);

    if (!enabled) {
      collection.delete(document.uri);
      return;
    }

    if (!compiler || typeof compiler.compileWireFile !== "function") {
      collection.delete(document.uri);
      return;
    }

    const source = document.getText();

    if (!source.trim()) {
      collection.delete(document.uri);
      return;
    }

    try {
      compiler.compileWireFile(source);

      collection.set(document.uri, []);
    } catch (error) {
      collection.set(document.uri, [toCompilerDiagnostic(document, error)]);
    }
  };

  /**
   * @param {vscode.TextDocument} document
   */
  const schedule = (document) => {
    if (document.languageId !== WRN_LANGUAGE_ID) {
      return;
    }

    const key = document.uri.toString();

    const existing = timers.get(key);

    if (existing) {
      clearTimeout(existing);
    }

    const timer = setTimeout(() => {
      timers.delete(key);
      run(document);
    }, 250);

    timers.set(key, timer);
  };

  for (const document of vscode.workspace.textDocuments) {
    run(document);
  }

  context.subscriptions.push(
    collection,

    vscode.workspace.onDidOpenTextDocument(run),

    vscode.workspace.onDidChangeTextDocument((event) => {
      schedule(event.document);
    }),

    vscode.workspace.onDidSaveTextDocument(run),

    vscode.workspace.onDidCloseTextDocument((document) => {
      const key = document.uri.toString();

      const timer = timers.get(key);

      if (timer) {
        clearTimeout(timer);
        timers.delete(key);
      }

      collection.delete(document.uri);
    }),

    vscode.workspace.onDidChangeConfiguration((event) => {
      if (!event.affectsConfiguration("wrnexus.diagnostics.enable")) {
        return;
      }

      for (const document of vscode.workspace.textDocuments) {
        run(document);
      }
    }),

    {
      dispose() {
        for (const timer of timers.values()) {
          clearTimeout(timer);
        }

        timers.clear();
      },
    },
  );
}

/**
 * Activate the WRNexus VS Code extension.
 *
 * @param {vscode.ExtensionContext} context
 */
function activate(context) {
  registerDiagnostics(context);
  registerCompilerDiagnostics(context);

  registerCompletionProvider(context);
  registerDefinitionProvider(context);
  registerFormatter(context);
}

function deactivate() {}

module.exports = {
  activate,
  deactivate,
  registerCompilerDiagnostics,
  registerFormatter,
  toCompilerDiagnostic,
};
