"use strict";

const vscode = require("vscode");

const COMPONENT_DECLARATION = /^\s*(component|layout)\s+([A-Za-z_$][\w$]*)\s*\{/gm;

function getTagAtPosition(document, position) {
  const range = document.getWordRangeAtPosition(position, /[A-Za-z_$][\w$]*/);

  if (!range) {
    return null;
  }

  const name = document.getText(range);

  if (!/^[A-Z][A-Za-z0-9_$]*$/.test(name)) {
    return null;
  }

  const line = document.lineAt(position.line).text;
  const offset = document.offsetAt(position);
  const lineStart = document.offsetAt(new vscode.Position(position.line, 0));

  const characterOffset = offset - lineStart;
  const before = line.slice(0, characterOffset);
  const after = line.slice(characterOffset);

  const insideTag = before.lastIndexOf("<") > before.lastIndexOf(">") && after.includes(">");

  if (!insideTag) {
    return null;
  }

  return {
    name,
    range,
  };
}

async function findDeclaration(name) {
  const files = await vscode.workspace.findFiles(
    "**/*.wrn",
    "**/{node_modules,dist,.wrnexus,.git}/**",
  );

  const matches = [];

  for (const uri of files) {
    let document;

    try {
      document = await vscode.workspace.openTextDocument(uri);
    } catch {
      continue;
    }

    const source = document.getText();

    COMPONENT_DECLARATION.lastIndex = 0;

    let match;

    while ((match = COMPONENT_DECLARATION.exec(source)) !== null) {
      const declarationName = match[2];

      if (declarationName !== name) {
        continue;
      }

      const nameOffset = match.index + match[0].indexOf(declarationName);

      const start = document.positionAt(nameOffset);
      const end = document.positionAt(nameOffset + declarationName.length);

      matches.push(new vscode.Location(uri, new vscode.Range(start, end)));
    }
  }

  return matches;
}

async function provideDefinition(document, position) {
  const tag = getTagAtPosition(document, position);

  if (!tag) {
    return null;
  }

  const matches = await findDeclaration(tag.name);

  if (matches.length === 0) {
    return null;
  }

  return matches.length === 1 ? matches[0] : matches;
}

function registerDefinitionProvider(context) {
  const disposable = vscode.languages.registerDefinitionProvider(
    {
      language: "wrn",
    },
    {
      provideDefinition,
    },
  );

  context.subscriptions.push(disposable);
}

module.exports = {
  findDeclaration,
  getTagAtPosition,
  provideDefinition,
  registerDefinitionProvider,
};
