import { relative, sep, posix } from "node:path";

const MODULES_DIR = "src/modules";

const rule = {
  meta: {
    type: "problem",
    docs: {
      description:
        "Across modules, only type-only imports from another module's index.ts are allowed.",
    },
    messages: {
      deepImportValue:
        "Cross-module imports must use the module root. Use `import type` from '{{module}}' instead of '{{source}}'.",
      deepImportType:
        "Cross-module imports must use the module root. Import from '{{module}}' instead of '{{source}}'.",
      importValue:
        "Cross-module imports must be type-only. Use `import type` to import from '{{source}}'.",
      importSideEffect:
        "Cross-module side-effect imports are not allowed. Remove the import of '{{source}}'.",
      importDynamic:
        "Cross-module dynamic imports are not allowed. Remove the dynamic import of '{{source}}'.",
      reExport:
        "Cross-module re-exports are not allowed. Remove the re-export of '{{source}}'; import what you need with `import type` instead.",
    },
  },

  create(context) {
    const filename = context.filename ?? context.getFilename();

    // cwd-relative, "/"-normalized so the rest is pure posix string math
    const relFile = relative(context.cwd ?? process.cwd(), filename)
      .split(sep)
      .join("/");
    if (!relFile.startsWith(MODULES_DIR + "/")) return {};

    const currentModule = relFile.slice((MODULES_DIR + "/").length).split("/")[0];

    return {
      // import ... from "x"
      ImportDeclaration(node) {
        check(context, node, currentModule, relFile);
      },
      // export { ... } from "x"
      ExportNamedDeclaration(node) {
        if (node.source) check(context, node, currentModule, relFile);
      },
      // export * from "x"
      ExportAllDeclaration(node) {
        if (node.source) check(context, node, currentModule, relFile);
      },
      // import("x")
      ImportExpression(node) {
        check(context, node, currentModule, relFile);
      },
    };
  },
};

// type-only: `import type ...` or every specifier inline `type` (no runtime binding crosses)
function isTypeOnly(node) {
  if (node.importKind === "type") return true;
  const specs = node.specifiers;
  return Array.isArray(specs) && specs.every((s) => s.importKind === "type");
}

function check(context, node, currentModule, relFile) {
  const source = node.source?.value;
  if (!source || !source.startsWith(".")) return;

  const relResolved = posix.join(posix.dirname(relFile), source);

  // outside src/modules → not our concern
  if (!relResolved.startsWith(MODULES_DIR + "/")) return;
  const targetParts = relResolved.slice((MODULES_DIR + "/").length).split("/");
  const targetModule = targetParts[0];

  if (targetModule === currentModule) return;

  // re-exporting another module's surface through this barrel blurs the boundary; not allowed
  if (node.type === "ExportNamedDeclaration" || node.type === "ExportAllDeclaration") {
    context.report({
      node: node.source,
      messageId: "reExport",
      data: { source },
    });
    return;
  }

  // dynamic import is always a runtime crossing with no type-only form; must be removed
  if (node.type === "ImportExpression") {
    context.report({
      node: node.source,
      messageId: "importDynamic",
      data: { source },
    });
    return;
  }

  // a side-effect import has no binding clause → can't become `import type`; must be removed
  if (node.type === "ImportDeclaration" && node.specifiers.length === 0) {
    context.report({
      node: node.source,
      messageId: "importSideEffect",
      data: { source },
    });
    return;
  }

  // cross-module: only the bare `../other` barrel is allowed, anything deeper is an error
  if (targetParts.length > 1) {
    const module = posix.relative(posix.dirname(relFile), MODULES_DIR + "/" + targetModule);
    context.report({
      node: node.source,
      messageId: isTypeOnly(node) ? "deepImportType" : "deepImportValue",
      data: { module, source },
    });
    return;
  }

  // barrel import: types ok, runtime values forbidden
  if (!isTypeOnly(node)) {
    context.report({
      node: node.source,
      messageId: "importValue",
      data: { source },
    });
  }
}

const plugin = {
  rules: {
    "no-cross-module-import": rule,
  },
};

export default plugin;
