/**
 * Rules Loader
 *
 * Loads markdown rules from .emdash/rules/ directory.
 * Rules are injected into the agent system prompt.
 */

import { promises as fs } from 'fs';
import path from 'path';
import { createLogger, type Logger } from '../utils/logger.js';

/**
 * Default rules directory
 */
const DEFAULT_RULES_DIR = '.emdash/rules';

/**
 * Separator between rule files
 */
const RULES_SEPARATOR = '\n\n---\n\n';

/**
 * Rules Loader Options
 */
export interface RulesLoaderOptions {
  /** Repository root directory */
  repoRoot?: string;
  /** Rules directory path (relative to repoRoot) */
  rulesDir?: string;
  /** Logger instance */
  logger?: Logger;
}

/**
 * Rules Loader
 *
 * Loads and manages markdown rules files.
 */
export class RulesLoader {
  private repoRoot: string;
  private rulesDir: string;
  private logger: Logger;
  private cache: Map<string, string> = new Map();

  constructor(options?: RulesLoaderOptions) {
    this.repoRoot = options?.repoRoot ?? process.cwd();
    this.rulesDir = path.join(
      this.repoRoot,
      options?.rulesDir ?? DEFAULT_RULES_DIR
    );
    this.logger = options?.logger ?? createLogger({ name: 'rules-loader' });
  }

  /**
   * Load all rules from the rules directory
   */
  async loadRules(): Promise<string> {
    try {
      const files = await fs.readdir(this.rulesDir);
      const mdFiles = files.filter((f) => f.endsWith('.md')).sort();

      if (mdFiles.length === 0) {
        this.logger.debug('No rule files found');
        return '';
      }

      const rules: string[] = [];

      for (const file of mdFiles) {
        const filePath = path.join(this.rulesDir, file);
        try {
          const content = await fs.readFile(filePath, 'utf-8');
          rules.push(content.trim());
          this.cache.set(file, content);
        } catch (error) {
          this.logger.warn({ file, error }, 'Failed to read rule file');
        }
      }

      this.logger.debug({ count: rules.length }, 'Loaded rules');
      return rules.join(RULES_SEPARATOR);
    } catch (error) {
      if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
        this.logger.debug('Rules directory not found');
        return '';
      }
      this.logger.error({ error }, 'Failed to load rules');
      return '';
    }
  }

  /**
   * Get rules for a specific agent
   *
   * Loads general.md first, then {agentName}.md if it exists.
   */
  async getRulesForAgent(agentName: string): Promise<string> {
    const rules: string[] = [];

    // Load general rules first
    const generalPath = path.join(this.rulesDir, 'general.md');
    try {
      const general = await fs.readFile(generalPath, 'utf-8');
      rules.push(general.trim());
    } catch {
      // No general rules
    }

    // Load agent-specific rules
    const agentPath = path.join(this.rulesDir, `${agentName}.md`);
    try {
      const agentRules = await fs.readFile(agentPath, 'utf-8');
      rules.push(agentRules.trim());
    } catch {
      // No agent-specific rules
    }

    if (rules.length === 0) {
      return '';
    }

    this.logger.debug({ agentName, ruleCount: rules.length }, 'Loaded agent rules');
    return rules.join(RULES_SEPARATOR);
  }

  /**
   * Format rules for inclusion in system prompt
   */
  formatRulesForPrompt(rules: string): string {
    if (!rules || rules.trim() === '') {
      return '';
    }

    return `
## Custom Rules

The following rules have been configured for this agent:

${rules}
`.trim();
  }

  /**
   * List available rule files
   */
  async listRules(): Promise<string[]> {
    try {
      const files = await fs.readdir(this.rulesDir);
      return files.filter((f) => f.endsWith('.md')).sort();
    } catch {
      return [];
    }
  }

  /**
   * Get a specific rule file content
   */
  async getRule(name: string): Promise<string | null> {
    const filename = name.endsWith('.md') ? name : `${name}.md`;
    const filePath = path.join(this.rulesDir, filename);

    try {
      return await fs.readFile(filePath, 'utf-8');
    } catch {
      return null;
    }
  }

  /**
   * Save a rule file
   */
  async saveRule(name: string, content: string): Promise<void> {
    const filename = name.endsWith('.md') ? name : `${name}.md`;
    const filePath = path.join(this.rulesDir, filename);

    // Ensure directory exists
    await fs.mkdir(this.rulesDir, { recursive: true });

    await fs.writeFile(filePath, content, 'utf-8');
    this.cache.set(filename, content);
    this.logger.info({ name }, 'Saved rule');
  }

  /**
   * Delete a rule file
   */
  async deleteRule(name: string): Promise<boolean> {
    const filename = name.endsWith('.md') ? name : `${name}.md`;
    const filePath = path.join(this.rulesDir, filename);

    try {
      await fs.unlink(filePath);
      this.cache.delete(filename);
      this.logger.info({ name }, 'Deleted rule');
      return true;
    } catch {
      return false;
    }
  }

  /**
   * Clear the cache
   */
  clearCache(): void {
    this.cache.clear();
  }
}

/**
 * Convenience functions
 */

let defaultLoader: RulesLoader | null = null;

/**
 * Get or create the default rules loader
 */
export function getRulesLoader(repoRoot?: string): RulesLoader {
  if (!defaultLoader) {
    defaultLoader = new RulesLoader({ repoRoot });
  }
  return defaultLoader;
}

/**
 * Load all rules
 */
export async function loadRules(rulesDir?: string): Promise<string> {
  const loader = new RulesLoader({ rulesDir });
  return loader.loadRules();
}

/**
 * Get rules for a specific agent
 */
export async function getRulesForAgent(
  agentName: string,
  rulesDir?: string
): Promise<string> {
  const loader = new RulesLoader({ rulesDir });
  return loader.getRulesForAgent(agentName);
}

/**
 * Format rules for system prompt
 */
export function formatRulesForPrompt(rules: string): string {
  const loader = new RulesLoader();
  return loader.formatRulesForPrompt(rules);
}

/**
 * Create a new rules loader
 */
export function createRulesLoader(options?: RulesLoaderOptions): RulesLoader {
  return new RulesLoader(options);
}
