// Generated by @harness-engineering/linter-gen
// Do not edit manually - regenerate from harness-linter.yml
// Config: {{meta.configPath}}

import { ESLintUtils, type TSESTree } from '@typescript-eslint/utils';
import { minimatch } from 'minimatch';
import * as path from 'path';

const createRule = ESLintUtils.RuleCreator(
  () => 'https://github.com/harness-engineering/linter-gen'
);

type MessageIds = 'circularDependency';

const ENTRY_POINTS: string[] = {{{json config.entryPoints}}};
const EXCLUDE_PATTERNS: string[] = {{{json config.exclude}}};
const MAX_DEPTH = {{#if config.maxDepth}}{{config.maxDepth}}{{else}}20{{/if}};

/**
 * Normalize path separators to forward slashes
 */
function normalizePath(filePath: string): string {
  return filePath.replace(/\\/g, '/');
}

/**
 * Check if a path matches any of the exclude patterns
 */
function isExcluded(filePath: string): boolean {
  const normalized = normalizePath(filePath);
  return EXCLUDE_PATTERNS.some((pattern) =>
    minimatch(normalized, pattern, { matchBase: true })
  );
}

// Track imports per file for cycle detection
const importGraph = new Map<string, Set<string>>();

export default createRule<[], MessageIds>({
  name: '{{name}}',
  meta: {
    type: 'problem',
    docs: {
      description: 'Detect circular import dependencies',
    },
    messages: {
      circularDependency:
        'Circular dependency detected: {{cycle}}',
    },
    schema: [],
  },
  defaultOptions: [],
  create(context) {
    const filePath = normalizePath(context.filename);

    if (isExcluded(filePath)) {
      return {};
    }

    const imports = new Set<string>();

    return {
      ImportDeclaration(node: TSESTree.ImportDeclaration) {
        const importPath = String(node.source.value);

        // Only track relative imports (internal dependencies)
        if (!importPath.startsWith('.')) {
          return;
        }

        // Resolve to absolute path
        const resolvedPath = normalizePath(
          path.resolve(path.dirname(context.filename), importPath)
        );

        imports.add(resolvedPath);
      },

      'Program:exit'() {
        // Store this file's imports
        importGraph.set(filePath, imports);

        // Check for cycles starting from this file
        const visited = new Set<string>();
        const stack: string[] = [];

        function detectCycle(current: string, depth: number): string[] | null {
          if (depth > MAX_DEPTH) return null;
          if (stack.includes(current)) {
            const cycleStart = stack.indexOf(current);
            return [...stack.slice(cycleStart), current];
          }
          if (visited.has(current)) return null;

          visited.add(current);
          stack.push(current);

          const deps = importGraph.get(current);
          if (deps) {
            for (const dep of deps) {
              const cycle = detectCycle(dep, depth + 1);
              if (cycle) return cycle;
            }
          }

          stack.pop();
          return null;
        }

        const cycle = detectCycle(filePath, 0);
        if (cycle) {
          context.report({
            loc: { line: 1, column: 0 },
            messageId: 'circularDependency',
            data: {
              cycle: cycle.map((p) => path.basename(p)).join(' → '),
            },
          });
        }
      },
    };
  },
});
