import { parse } from "@babel/parser";
import _traverse, { type NodePath } from "@babel/traverse";
import * as t from "@babel/types";
import type { Fact } from "@fragments-sdk/core";

import { BABEL_PARSER_OPTIONS } from "./babel-config.js";
import { locFromNode } from "./babel-helpers.js";
import type { ReactComponentAdoptionUnresolvedImport } from "./component-adoption-types.js";

const traverse =
  (_traverse as unknown as { default?: typeof _traverse }).default ??
  _traverse;

export function unresolvedImportsForFile(
  facts: readonly Fact[],
): ReactComponentAdoptionUnresolvedImport[] {
  const importsByLocal = new Map(
    facts
      .filter((fact) => fact.kind === "usage_import")
      .map((fact) => [fact.local, fact]),
  );
  const resolvedNodeIds = new Set(
    facts
      .filter((fact) => fact.kind === "usage_component")
      .map((fact) => fact.nodeId),
  );
  const out: ReactComponentAdoptionUnresolvedImport[] = [];

  for (const fact of facts) {
    if (fact.kind !== "usage_node" || resolvedNodeIds.has(fact.id)) continue;
    const local = fact.element.split(".")[0] ?? fact.element;
    const importFact = importsByLocal.get(local);
    if (!importFact) continue;
    out.push({
      filePath: fact.file,
      line: fact.location.line,
      column: fact.location.column,
      elementName: fact.element,
      local,
      imported: importFact.imported,
      source: importFact.source,
      reason: "unresolved",
    });
  }

  return out;
}

export function callSiteOwners(
  source: string,
  filePath: string,
): Map<string, string> {
  const out = new Map<string, string>();
  let ast: t.File;
  try {
    ast = parse(source, BABEL_PARSER_OPTIONS);
  } catch {
    return out;
  }

  traverse(ast, {
    JSXElement(path) {
      const location = locFromNode(path.node.openingElement, filePath);
      if (!location) return;
      const ownerName = ownerComponentName(path);
      if (!ownerName) return;
      out.set(
        callSiteKey({
          filePath,
          line: location.line,
          column: location.column,
        }),
        ownerName,
      );
    },
  });

  return out;
}

export function callSiteKey(input: {
  filePath: string;
  line: number;
  column: number;
}): string {
  return `${input.filePath}:${input.line}:${input.column}`;
}

function ownerComponentName(path: NodePath<t.JSXElement>): string | undefined {
  let current: NodePath<t.Node> | null = path.parentPath;
  while (current) {
    const name = componentNameForAncestor(current);
    if (name && isComponentLikeName(name)) return name;
    current = current.parentPath;
  }
  return undefined;
}

function componentNameForAncestor(path: NodePath<t.Node>): string | undefined {
  if (path.isFunctionDeclaration() || path.isClassDeclaration()) {
    return path.node.id?.name;
  }
  if (path.isFunctionExpression() || path.isArrowFunctionExpression()) {
    return variableNameForFunction(path);
  }
  if (path.isObjectMethod()) {
    return t.isIdentifier(path.node.key) ? path.node.key.name : undefined;
  }
  return undefined;
}

function variableNameForFunction(path: NodePath<t.Node>): string | undefined {
  let current = path.parentPath;
  while (
    current &&
    (current.isCallExpression() ||
      current.isTSAsExpression() ||
      current.isTSSatisfiesExpression())
  ) {
    current = current.parentPath;
  }
  if (current?.isVariableDeclarator() && t.isIdentifier(current.node.id)) {
    return current.node.id.name;
  }
  return undefined;
}

function isComponentLikeName(name: string): boolean {
  return /^[A-Z]/.test(name);
}
