/**
 * .codebuddyrules File Support
 *
 * Project-specific AI behavior configuration:
 * - Custom instructions per project
 * - Code style preferences
 * - Framework-specific guidelines
 * - Ignore patterns
 * - Security policies
 *
 * Inspired by Cursor's .cursorrules feature.
 */
import { EventEmitter } from 'events';
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import * as yaml from 'js-yaml';
// ============================================================================
// Default Configuration
// ============================================================================
const DEFAULT_CONFIG = {
    searchPaths: ['.', '.codebuddy', '.config'],
    fileNames: [
        '.codebuddyrules',
        '.codebuddyrules.yaml',
        '.codebuddyrules.yml',
        '.codebuddyrules.json',
        'codebuddyrules.md',
        // Local files (git-ignored, personal overrides) - Claude Code style
        '.codebuddyrules.local',
        '.codebuddyrules.local.yaml',
        '.codebuddyrules.local.yml',
        '.codebuddyrules.local.json',
        '.codebuddyrules.local.md',
    ],
    inheritFromParent: true,
    enableGlobalRules: true,
};
const DEFAULT_RULES = {
    version: '1.0',
    style: {
        indentation: 'spaces',
        indentSize: 2,
        quotes: 'single',
        semicolons: true,
    },
    naming: {
        variables: 'camelCase',
        functions: 'camelCase',
        classes: 'PascalCase',
        files: 'kebab-case',
        constants: 'UPPER_SNAKE_CASE',
    },
    security: {
        confirmDestructive: true,
        blockedCommands: ['rm -rf /', 'dd if=/dev/zero'],
        blockedPaths: ['/etc', '/usr', '/bin', '/sbin'],
    },
    git: {
        conventionalCommits: true,
        commitFormat: '<type>(<scope>): <description>',
    },
};
// ============================================================================
// CodeBuddyRules Manager
// ============================================================================
export class CodeBuddyRulesManager extends EventEmitter {
    config;
    rules = {};
    loadedFiles = [];
    initialized = false;
    constructor(config = {}) {
        super();
        this.config = { ...DEFAULT_CONFIG, ...config };
    }
    /**
     * Initialize and load rules for a directory
     */
    async initialize(workingDir = process.cwd()) {
        this.rules = { ...DEFAULT_RULES };
        this.loadedFiles = [];
        // Load global rules first
        if (this.config.enableGlobalRules) {
            const globalRules = await this.loadGlobalRules();
            if (globalRules) {
                this.rules = this.mergeRules(this.rules, globalRules);
            }
        }
        // Load project rules (with inheritance)
        if (this.config.inheritFromParent) {
            const hierarchy = this.getDirectoryHierarchy(workingDir);
            for (const dir of hierarchy) {
                const dirRules = await this.loadRulesFromDirectory(dir);
                if (dirRules) {
                    this.rules = this.mergeRules(this.rules, dirRules);
                }
            }
        }
        else {
            const dirRules = await this.loadRulesFromDirectory(workingDir);
            if (dirRules) {
                this.rules = this.mergeRules(this.rules, dirRules);
            }
        }
        this.initialized = true;
        this.emit('initialized', { rules: this.rules, files: this.loadedFiles });
    }
    /**
     * Get current rules
     */
    getRules() {
        return { ...this.rules };
    }
    /**
     * Get loaded files
     */
    getLoadedFiles() {
        return [...this.loadedFiles];
    }
    /**
     * Check if rules are loaded
     */
    isInitialized() {
        return this.initialized;
    }
    /**
     * Generate system prompt additions from rules
     */
    getSystemPromptAdditions() {
        const sections = [];
        // Description
        if (this.rules.description) {
            sections.push(`Project: ${this.rules.description}`);
        }
        // Languages and frameworks
        if (this.rules.languages?.length) {
            sections.push(`Languages: ${this.rules.languages.join(', ')}`);
        }
        if (this.rules.frameworks?.length) {
            sections.push(`Frameworks: ${this.rules.frameworks.join(', ')}`);
        }
        // Custom instructions
        if (this.rules.instructions?.length) {
            sections.push('');
            sections.push('Project Guidelines:');
            for (const instruction of this.rules.instructions) {
                sections.push(`- ${instruction}`);
            }
        }
        // Code style
        if (this.rules.style) {
            sections.push('');
            sections.push('Code Style:');
            if (this.rules.style.indentation) {
                sections.push(`- Use ${this.rules.style.indentation} for indentation (${this.rules.style.indentSize || 2} spaces)`);
            }
            if (this.rules.style.quotes) {
                sections.push(`- Use ${this.rules.style.quotes} quotes`);
            }
            if (this.rules.style.semicolons !== undefined) {
                sections.push(`- ${this.rules.style.semicolons ? 'Use' : 'Omit'} semicolons`);
            }
            if (this.rules.style.maxLineLength) {
                sections.push(`- Max line length: ${this.rules.style.maxLineLength} characters`);
            }
        }
        // Naming conventions
        if (this.rules.naming) {
            sections.push('');
            sections.push('Naming Conventions:');
            if (this.rules.naming.variables) {
                sections.push(`- Variables: ${this.rules.naming.variables}`);
            }
            if (this.rules.naming.functions) {
                sections.push(`- Functions: ${this.rules.naming.functions}`);
            }
            if (this.rules.naming.classes) {
                sections.push(`- Classes: ${this.rules.naming.classes}`);
            }
            if (this.rules.naming.files) {
                sections.push(`- Files: ${this.rules.naming.files}`);
            }
        }
        // Testing
        if (this.rules.testing) {
            sections.push('');
            sections.push('Testing:');
            if (this.rules.testing.framework) {
                sections.push(`- Framework: ${this.rules.testing.framework}`);
            }
            if (this.rules.testing.pattern) {
                sections.push(`- Test files: ${this.rules.testing.pattern}`);
            }
        }
        // Git
        if (this.rules.git?.conventionalCommits) {
            sections.push('');
            sections.push('Git:');
            sections.push('- Use conventional commits format');
            if (this.rules.git.commitFormat) {
                sections.push(`- Format: ${this.rules.git.commitFormat}`);
            }
        }
        // Persona
        if (this.rules.persona) {
            sections.push('');
            if (this.rules.persona.name) {
                sections.push(`Act as: ${this.rules.persona.name}`);
            }
            if (this.rules.persona.tone) {
                sections.push(`Tone: ${this.rules.persona.tone}`);
            }
            if (this.rules.persona.expertise?.length) {
                sections.push(`Expertise: ${this.rules.persona.expertise.join(', ')}`);
            }
        }
        // Raw instructions
        if (this.rules.raw) {
            sections.push('');
            sections.push(this.rules.raw);
        }
        return sections.join('\n');
    }
    /**
     * Get ignore patterns
     */
    getIgnorePatterns() {
        return [...(this.rules.ignore || []), ...(this.rules.exclude || [])];
    }
    /**
     * Get include patterns
     */
    getIncludePatterns() {
        return [...(this.rules.include || [])];
    }
    /**
     * Check if command is allowed
     */
    isCommandAllowed(command) {
        if (!this.rules.security)
            return true;
        // Check blocked commands
        if (this.rules.security.blockedCommands) {
            for (const blocked of this.rules.security.blockedCommands) {
                if (command.includes(blocked)) {
                    return false;
                }
            }
        }
        // Check allowed commands (if specified, only these are allowed)
        if (this.rules.security.allowedCommands?.length) {
            const cmd = command.split(/\s+/)[0];
            return this.rules.security.allowedCommands.includes(cmd);
        }
        return true;
    }
    /**
     * Check if path is allowed
     */
    isPathAllowed(filePath) {
        if (!this.rules.security?.blockedPaths)
            return true;
        const resolved = path.resolve(filePath);
        for (const blocked of this.rules.security.blockedPaths) {
            if (resolved.startsWith(blocked)) {
                return false;
            }
        }
        return true;
    }
    /**
     * Get custom prompt by name
     */
    getCustomPrompt(name) {
        return this.rules.prompts?.[name];
    }
    /**
     * Create default rules file
     */
    async createDefaultRules(targetDir) {
        const rulesPath = path.join(targetDir, '.codebuddyrules');
        const defaultContent = `# Grok Rules
# Project-specific AI behavior configuration

# Project info
description: "My Project"
languages:
  - typescript
  - javascript
frameworks:
  - react
  - node

# Custom instructions for the AI
instructions:
  - "Follow the project's existing code style"
  - "Write tests for new functionality"
  - "Use meaningful variable names"

# Code style
style:
  indentation: spaces
  indentSize: 2
  quotes: single
  semicolons: true
  trailingCommas: es5

# Naming conventions
naming:
  variables: camelCase
  functions: camelCase
  classes: PascalCase
  files: kebab-case
  constants: UPPER_SNAKE_CASE

# Testing
testing:
  framework: jest
  pattern: "**/*.test.ts"
  directory: tests

# Git
git:
  conventionalCommits: true
  commitFormat: "<type>(<scope>): <description>"

# Context management
context:
  maxFiles: 20
  priorityFiles:
    - README.md
    - package.json
    - tsconfig.json

# Files to ignore
ignore:
  - node_modules/**
  - dist/**
  - coverage/**
  - "*.log"
`;
        fs.writeFileSync(rulesPath, defaultContent);
        this.emit('rules:created', { path: rulesPath });
        return rulesPath;
    }
    /**
     * Format rules summary
     */
    formatSummary() {
        const lines = [
            '📋 Grok Rules',
            '═'.repeat(40),
            '',
        ];
        if (this.loadedFiles.length === 0) {
            lines.push('No .codebuddyrules file found.');
            lines.push('');
            lines.push('Create one with: /rules init');
        }
        else {
            lines.push('Loaded files:');
            for (const file of this.loadedFiles) {
                lines.push(`  • ${file}`);
            }
            lines.push('');
            if (this.rules.description) {
                lines.push(`Project: ${this.rules.description}`);
            }
            if (this.rules.languages?.length) {
                lines.push(`Languages: ${this.rules.languages.join(', ')}`);
            }
            if (this.rules.frameworks?.length) {
                lines.push(`Frameworks: ${this.rules.frameworks.join(', ')}`);
            }
            if (this.rules.instructions?.length) {
                lines.push('');
                lines.push(`Instructions: ${this.rules.instructions.length} custom rules`);
            }
            if (this.rules.ignore?.length) {
                lines.push(`Ignore patterns: ${this.rules.ignore.length}`);
            }
        }
        return lines.join('\n');
    }
    // ============================================================================
    // Private Methods
    // ============================================================================
    async loadGlobalRules() {
        const homeDir = os.homedir();
        const globalPaths = [
            path.join(homeDir, '.codebuddyrules'),
            path.join(homeDir, '.config', 'codebuddy', 'rules.yaml'),
            path.join(homeDir, '.codebuddy', 'rules.yaml'),
        ];
        for (const globalPath of globalPaths) {
            const rules = await this.loadRulesFile(globalPath);
            if (rules) {
                return rules;
            }
        }
        return null;
    }
    async loadRulesFromDirectory(dir) {
        for (const searchPath of this.config.searchPaths) {
            for (const fileName of this.config.fileNames) {
                const filePath = path.join(dir, searchPath, fileName);
                const rules = await this.loadRulesFile(filePath);
                if (rules) {
                    return rules;
                }
            }
        }
        return null;
    }
    async loadRulesFile(filePath) {
        try {
            if (!fs.existsSync(filePath)) {
                return null;
            }
            const content = fs.readFileSync(filePath, 'utf-8');
            let rules;
            const ext = path.extname(filePath).toLowerCase();
            if (ext === '.json') {
                rules = JSON.parse(content);
            }
            else if (ext === '.md') {
                // Parse markdown format (extract YAML frontmatter or code blocks)
                rules = this.parseMarkdownRules(content);
            }
            else {
                // YAML format (default)
                rules = yaml.load(content);
            }
            this.loadedFiles.push(filePath);
            this.emit('rules:loaded', { path: filePath, rules });
            return rules;
        }
        catch (error) {
            this.emit('rules:error', { path: filePath, error });
            return null;
        }
    }
    parseMarkdownRules(content) {
        // Try YAML frontmatter
        const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
        if (frontmatterMatch) {
            return yaml.load(frontmatterMatch[1]);
        }
        // Try YAML code block
        const codeBlockMatch = content.match(/```ya?ml\n([\s\S]*?)\n```/);
        if (codeBlockMatch) {
            return yaml.load(codeBlockMatch[1]);
        }
        // Treat entire content as raw instructions
        return {
            raw: content,
        };
    }
    getDirectoryHierarchy(dir) {
        const hierarchy = [];
        let current = path.resolve(dir);
        const root = path.parse(current).root;
        while (current !== root) {
            hierarchy.unshift(current);
            current = path.dirname(current);
        }
        return hierarchy;
    }
    mergeRules(base, override) {
        const merged = { ...base };
        // Simple properties
        if (override.version)
            merged.version = override.version;
        if (override.description)
            merged.description = override.description;
        if (override.raw)
            merged.raw = (base.raw || '') + '\n' + override.raw;
        // Arrays (concatenate)
        if (override.languages)
            merged.languages = [...(base.languages || []), ...override.languages];
        if (override.frameworks)
            merged.frameworks = [...(base.frameworks || []), ...override.frameworks];
        if (override.instructions)
            merged.instructions = [...(base.instructions || []), ...override.instructions];
        if (override.include)
            merged.include = [...(base.include || []), ...override.include];
        if (override.exclude)
            merged.exclude = [...(base.exclude || []), ...override.exclude];
        if (override.ignore)
            merged.ignore = [...(base.ignore || []), ...override.ignore];
        // Objects (deep merge)
        if (override.style)
            merged.style = { ...base.style, ...override.style };
        if (override.naming)
            merged.naming = { ...base.naming, ...override.naming };
        if (override.security)
            merged.security = { ...base.security, ...override.security };
        if (override.testing)
            merged.testing = { ...base.testing, ...override.testing };
        if (override.documentation)
            merged.documentation = { ...base.documentation, ...override.documentation };
        if (override.git)
            merged.git = { ...base.git, ...override.git };
        if (override.context)
            merged.context = { ...base.context, ...override.context };
        if (override.persona)
            merged.persona = { ...base.persona, ...override.persona };
        if (override.prompts)
            merged.prompts = { ...base.prompts, ...override.prompts };
        return merged;
    }
    /**
     * Dispose and clean up resources
     */
    dispose() {
        this.removeAllListeners();
    }
}
// ============================================================================
// Singleton
// ============================================================================
let managerInstance = null;
export function getCodeBuddyRulesManager(config) {
    if (!managerInstance) {
        managerInstance = new CodeBuddyRulesManager(config);
    }
    return managerInstance;
}
export async function initializeCodeBuddyRules(workingDir) {
    const manager = getCodeBuddyRulesManager();
    await manager.initialize(workingDir);
    return manager;
}
export function resetCodeBuddyRulesManager() {
    if (managerInstance) {
        managerInstance.dispose();
    }
    managerInstance = null;
}
//# sourceMappingURL=codebuddyrules.js.map