"""
Domain management for CARL Control Panel.
Handles CRUD operations for CARL domains and their rules.
"""
import re
from pathlib import Path
from datetime import datetime


class DomainManager:
    """Manages CRUD operations for CARL domains."""

    # Special domains with different handling
    PROTECTED_DOMAINS = {'GLOBAL'}  # Cannot delete
    SPECIAL_FORMAT_DOMAINS = {'CONTEXT', 'COMMANDS'}  # Different file formats

    def __init__(self, carl_path: Path):
        """Initialize with path to .carl directory."""
        self.carl_path = Path(carl_path)
        self.manifest_path = self.carl_path / 'manifest'

    def exists(self) -> bool:
        """Check if CARL directory exists."""
        return self.carl_path.exists() and self.manifest_path.exists()

    # =========================================================================
    # Domain CRUD Operations
    # =========================================================================

    def list_domains(self) -> dict:
        """
        List all domains with summary info.

        Returns:
            {
                'success': bool,
                'domains': [
                    {
                        'name': str,
                        'state': bool,
                        'always_on': bool,
                        'rule_count': int,
                        'recall_keywords': list[str],
                        'has_file': bool,
                        'is_special': bool,
                        'is_protected': bool
                    }
                ]
            }
        """
        if not self.exists():
            return {'success': False, 'error': 'CARL installation not found'}

        domains = []
        manifest_entries = self._parse_manifest()

        # Get all domain files
        domain_files = set()
        excluded_names = {'manifest', 'sessions', 'manifest.env', 'sessions.env'}
        excluded_extensions = {'.json', '.jsonc', '.md', '.txt', '.log'}
        for f in self.carl_path.iterdir():
            if f.is_file() and f.name.lower() not in excluded_names and not f.name.startswith('.'):
                # Skip non-domain files (json, markdown, etc.)
                if f.suffix.lower() in excluded_extensions:
                    continue
                # Strip .env extension if present
                name = f.stem.upper() if f.suffix.lower() == '.env' else f.name.upper()
                domain_files.add(name)

        # Collect all domain names from both manifest and files
        all_domains = set()
        for key in manifest_entries:
            if key.endswith('_STATE'):
                domain_name = key[:-6]  # Remove _STATE suffix
                all_domains.add(domain_name)
        all_domains.update(domain_files)

        # Get domain order from manifest (if specified)
        domain_order_str = manifest_entries.get('DOMAIN_ORDER', '')
        ordered_domains = [d.strip() for d in domain_order_str.split(',') if d.strip()]

        # Build final order: specified order first, then remaining alphabetically
        final_order = []
        remaining = set(all_domains)
        for domain in ordered_domains:
            if domain in remaining:
                final_order.append(domain)
                remaining.remove(domain)
        final_order.extend(sorted(remaining))

        for domain_name in final_order:
            domain_lower = domain_name.lower()
            # Check both with and without .env extension
            file_path = self.carl_path / domain_lower
            if not file_path.exists():
                file_path = self.carl_path / f'{domain_lower}.env'
            has_file = file_path.exists()

            # Get state (default to active if file exists but no manifest entry)
            state_key = f'{domain_name}_STATE'
            state_value = manifest_entries.get(state_key, 'active' if has_file else 'inactive')
            is_active = state_value.lower() in ['active', 'true', '1', 'yes', 'on']

            # Get always_on
            always_on_key = f'{domain_name}_ALWAYS_ON'
            always_on_value = manifest_entries.get(always_on_key, 'false')
            is_always_on = always_on_value.lower() in ['true', '1', 'yes', 'on']

            # Get recall keywords
            recall_key = f'{domain_name}_RECALL'
            recall_str = manifest_entries.get(recall_key, '')
            recall_keywords = [k.strip() for k in recall_str.split(',') if k.strip()]

            # Count rules (and commands for COMMANDS domain)
            rule_count = 0
            command_count = 0
            if has_file:
                rule_count = self._count_rules(file_path, domain_name)
                # For COMMANDS domain, also count the number of commands
                if domain_name == 'COMMANDS':
                    commands = self._parse_commands(file_path)
                    # Exclude _order key from count
                    command_count = len([k for k in commands.keys() if k != '_order'])

            domain_info = {
                'name': domain_name,
                'state': is_active,
                'always_on': is_always_on,
                'rule_count': rule_count,
                'recall_keywords': recall_keywords,
                'has_file': has_file,
                'is_special': domain_name in self.SPECIAL_FORMAT_DOMAINS,
                'is_protected': domain_name in self.PROTECTED_DOMAINS
            }
            # Add command_count only for COMMANDS domain
            if domain_name == 'COMMANDS':
                domain_info['command_count'] = command_count

            domains.append(domain_info)

        return {'success': True, 'domains': domains}

    def get_domain(self, name: str) -> dict:
        """
        Get detailed info for a specific domain.

        Args:
            name: Domain name (case-insensitive)

        Returns:
            {
                'success': bool,
                'name': str,
                'state': bool,
                'always_on': bool,
                'recall': str,
                'exclude': str,
                'rules': [{'index': int, 'text': str}],
                'file_path': str,
                'is_special': bool,
                'is_protected': bool
            }
        """
        if not self.exists():
            return {'success': False, 'error': 'CARL installation not found'}

        domain_name = self._sanitize_name(name)
        domain_lower = domain_name.lower()
        # Check both with and without .env extension
        file_path = self.carl_path / domain_lower
        if not file_path.exists():
            file_path = self.carl_path / f'{domain_lower}.env'

        manifest_entries = self._parse_manifest()

        # Get config from manifest
        state_key = f'{domain_name}_STATE'
        state_value = manifest_entries.get(state_key, 'inactive')
        is_active = state_value.lower() in ['active', 'true', '1', 'yes', 'on']

        always_on_key = f'{domain_name}_ALWAYS_ON'
        always_on_value = manifest_entries.get(always_on_key, 'false')
        is_always_on = always_on_value.lower() in ['true', '1', 'yes', 'on']

        recall_key = f'{domain_name}_RECALL'
        recall_str = manifest_entries.get(recall_key, '')

        exclude_key = f'{domain_name}_EXCLUDE'
        exclude_str = manifest_entries.get(exclude_key, '')

        # Get rules from file - special handling for CONTEXT and COMMANDS
        rules = []
        special_data = None

        if file_path.exists():
            if domain_name == 'CONTEXT':
                special_data = self._parse_context_brackets(file_path)
            elif domain_name == 'COMMANDS':
                special_data = self._parse_commands(file_path)
            else:
                rules = self._parse_rules(file_path, domain_name)

        result = {
            'success': True,
            'name': domain_name,
            'state': is_active,
            'always_on': is_always_on,
            'recall': recall_str,
            'exclude': exclude_str,
            'rules': rules,
            'file_path': str(file_path) if file_path.exists() else None,
            'is_special': domain_name in self.SPECIAL_FORMAT_DOMAINS,
            'is_protected': domain_name in self.PROTECTED_DOMAINS
        }

        # Add special data for CONTEXT/COMMANDS
        if domain_name == 'CONTEXT':
            result['brackets'] = special_data if special_data else {
                'FRESH': {'enabled': True, 'rules': []},
                'MODERATE': {'enabled': True, 'rules': []},
                'DEPLETED': {'enabled': True, 'rules': []}
            }
        elif domain_name == 'COMMANDS':
            result['commands'] = special_data if special_data else {'_order': []}

        return result

    def create_domain(self, name: str, config: dict) -> dict:
        """
        Create a new domain with file and manifest entries.

        Args:
            name: Domain name (will be uppercased)
            config: {
                'recall': str (comma-separated keywords),
                'exclude': str (optional),
                'always_on': bool,
                'state': bool,
                'rules': list[str] (rule texts, index 0 is description)
            }

        Returns:
            {'success': bool, 'error': str | None}
        """
        if not self.exists():
            return {'success': False, 'error': 'CARL installation not found'}

        domain_name = self._sanitize_name(name)
        if not domain_name:
            return {'success': False, 'error': 'Invalid domain name'}

        domain_lower = domain_name.lower()
        # Check if domain already exists (both formats)
        existing_path = self._get_domain_file_path(domain_name)
        if existing_path.exists():
            return {'success': False, 'error': f'Domain {domain_name} already exists'}

        # Create with .env extension
        file_path = self.carl_path / f'{domain_lower}.env'

        try:
            # Create domain file
            rules = config.get('rules', [])
            self._write_domain_file(file_path, domain_name, rules)

            # Add manifest entries
            self._add_manifest_entries(domain_name, config)

            return {'success': True, 'name': domain_name, 'file_path': str(file_path)}
        except Exception as e:
            return {'success': False, 'error': str(e)}

    def update_domain(self, name: str, config: dict) -> dict:
        """
        Update domain configuration (manifest entries and/or rules).

        Args:
            name: Domain name
            config: {
                'recall': str (optional),
                'exclude': str (optional),
                'always_on': bool (optional),
                'state': bool (optional),
                'rules': list[str] (optional - if provided, rewrites entire rules)
            }

        Returns:
            {'success': bool, 'error': str | None}
        """
        if not self.exists():
            return {'success': False, 'error': 'CARL installation not found'}

        domain_name = self._sanitize_name(name)
        file_path = self._get_domain_file_path(domain_name)

        try:
            # Update manifest entries
            self._update_manifest_entries(domain_name, config)

            # Update rules if provided
            if 'rules' in config:
                rules = config['rules']
                self._write_domain_file(file_path, domain_name, rules)

            return {'success': True}
        except Exception as e:
            return {'success': False, 'error': str(e)}

    def delete_domain(self, name: str) -> dict:
        """
        Delete a domain (file + manifest entries).

        Args:
            name: Domain name

        Returns:
            {'success': bool, 'error': str | None}
        """
        if not self.exists():
            return {'success': False, 'error': 'CARL installation not found'}

        domain_name = self._sanitize_name(name)

        # Check if protected
        if domain_name in self.PROTECTED_DOMAINS:
            return {'success': False, 'error': f'Cannot delete protected domain: {domain_name}'}

        file_path = self._get_domain_file_path(domain_name)

        try:
            # Remove file if exists
            if file_path.exists():
                file_path.unlink()

            # Remove manifest entries
            self._remove_manifest_entries(domain_name)

            return {'success': True}
        except Exception as e:
            return {'success': False, 'error': str(e)}

    def reorder_domains(self, order: list) -> dict:
        """
        Reorder domains by setting DOMAIN_ORDER in manifest.

        Args:
            order: List of domain names in desired order

        Returns:
            {'success': bool, 'error': str | None}
        """
        if not self.exists():
            return {'success': False, 'error': 'CARL installation not found'}

        try:
            # Sanitize domain names
            sanitized_order = [self._sanitize_name(d) for d in order if d]
            order_str = ','.join(sanitized_order)

            # Update or add DOMAIN_ORDER in manifest
            manifest_content = self.manifest_path.read_text()
            lines = manifest_content.split('\n')
            new_lines = []
            found = False

            for line in lines:
                if line.startswith('DOMAIN_ORDER='):
                    new_lines.append(f'DOMAIN_ORDER={order_str}')
                    found = True
                else:
                    new_lines.append(line)

            if not found:
                # Add after the header comments
                insert_idx = 0
                for i, line in enumerate(new_lines):
                    if line and not line.startswith('#'):
                        insert_idx = i
                        break
                new_lines.insert(insert_idx, f'DOMAIN_ORDER={order_str}')
                new_lines.insert(insert_idx + 1, '')

            self.manifest_path.write_text('\n'.join(new_lines))
            return {'success': True}
        except Exception as e:
            return {'success': False, 'error': str(e)}

    # =========================================================================
    # Special Domain Operations (CONTEXT & COMMANDS)
    # =========================================================================

    def update_context_bracket(self, bracket: str, data: dict) -> dict:
        """
        Update a CONTEXT bracket.

        Args:
            bracket: FRESH, MODERATE, or DEPLETED
            data: {
                'enabled': bool (optional),
                'rules': [{'index': int, 'text': str}] (optional - full replacement)
            }

        Returns:
            {'success': bool, 'error': str | None}
        """
        if not self.exists():
            return {'success': False, 'error': 'CARL installation not found'}

        bracket = bracket.upper()
        if bracket not in ['FRESH', 'MODERATE', 'DEPLETED']:
            return {'success': False, 'error': f'Invalid bracket: {bracket}'}

        file_path = self.carl_path / 'context'
        if not file_path.exists():
            return {'success': False, 'error': 'Context file not found'}

        try:
            # Parse current state
            brackets = self._parse_context_brackets(file_path)

            # Update enabled if provided
            if 'enabled' in data:
                brackets[bracket]['enabled'] = bool(data['enabled'])

            # Update rules if provided
            if 'rules' in data:
                brackets[bracket]['rules'] = data['rules']

            # Write back
            self._write_context_file(file_path, brackets)
            return {'success': True}
        except Exception as e:
            return {'success': False, 'error': str(e)}

    def update_command(self, command: str, data: dict) -> dict:
        """
        Update a star command in COMMANDS domain.

        Args:
            command: Command name (e.g., BRIEF, DISCUSS)
            data: {
                'rules': [{'index': int, 'text': str}] (full replacement)
            }

        Returns:
            {'success': bool, 'error': str | None}
        """
        if not self.exists():
            return {'success': False, 'error': 'CARL installation not found'}

        command = command.upper()
        file_path = self.carl_path / 'commands'
        if not file_path.exists():
            return {'success': False, 'error': 'Commands file not found'}

        try:
            # Parse current state
            commands = self._parse_commands(file_path)

            # Update or create command
            if 'rules' in data:
                commands[command] = {'rules': data['rules']}

            # Write back
            self._write_commands_file(file_path, commands)
            return {'success': True}
        except Exception as e:
            return {'success': False, 'error': str(e)}

    def delete_command(self, command: str) -> dict:
        """Delete a star command from COMMANDS domain."""
        if not self.exists():
            return {'success': False, 'error': 'CARL installation not found'}

        command = command.upper()
        file_path = self.carl_path / 'commands'
        if not file_path.exists():
            return {'success': False, 'error': 'Commands file not found'}

        try:
            commands = self._parse_commands(file_path)

            if command not in commands:
                return {'success': False, 'error': f'Command not found: {command}'}

            del commands[command]
            # Also remove from order
            if '_order' in commands and command in commands['_order']:
                commands['_order'].remove(command)
            self._write_commands_file(file_path, commands)
            return {'success': True}
        except Exception as e:
            return {'success': False, 'error': str(e)}

    def reorder_commands(self, order: list) -> dict:
        """
        Reorder star commands by setting _order in commands file.

        Args:
            order: List of command names in desired order

        Returns:
            {'success': bool, 'error': str | None}
        """
        if not self.exists():
            return {'success': False, 'error': 'CARL installation not found'}

        file_path = self.carl_path / 'commands'
        if not file_path.exists():
            return {'success': False, 'error': 'Commands file not found'}

        try:
            commands = self._parse_commands(file_path)

            # Sanitize and validate order
            sanitized_order = [cmd.upper() for cmd in order if cmd]
            actual_commands = [k for k in commands.keys() if k != '_order']

            # Verify all commands exist
            for cmd in sanitized_order:
                if cmd not in actual_commands:
                    return {'success': False, 'error': f'Command not found: {cmd}'}

            commands['_order'] = sanitized_order
            self._write_commands_file(file_path, commands)
            return {'success': True}
        except Exception as e:
            return {'success': False, 'error': str(e)}

    def _write_context_file(self, file_path: Path, brackets: dict) -> None:
        """Write context file from bracket structure."""
        timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')

        lines = [
            "# Dynamic Rules Loader V2 - Context-Adaptive Rules",
            "# =================================================",
            f"# Updated: {timestamp}",
            "#",
            "# Format:",
            "#   {BRACKET}_RULES=true/false  - Enable/disable bracket",
            "#   {BRACKET}_RULE_{N}=text     - Individual rules",
            "",
            "# Bracket Control Flags"
        ]

        # Write enabled flags
        for bracket in ['FRESH', 'MODERATE', 'DEPLETED']:
            enabled = 'true' if brackets.get(bracket, {}).get('enabled', True) else 'false'
            lines.append(f"{bracket}_RULES={enabled}")

        # Write rules for each bracket
        for bracket in ['FRESH', 'MODERATE', 'DEPLETED']:
            lines.append("")
            lines.append(f"# {'=' * 76}")
            lines.append(f"# {bracket} Bracket Rules")
            lines.append(f"# {'=' * 76}")

            rules = brackets.get(bracket, {}).get('rules', [])
            for rule in rules:
                idx = rule.get('index', 0)
                text = rule.get('text', '')
                lines.append(f"{bracket}_RULE_{idx}={text}")

        lines.append("")
        file_path.write_text('\n'.join(lines))

    def _write_commands_file(self, file_path: Path, commands: dict) -> None:
        """Write commands file from command structure."""
        timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')

        # Get command order (excluding _order key itself)
        command_order = commands.get('_order', [])
        actual_commands = [k for k in commands.keys() if k != '_order']

        # Build final order: specified order first, then remaining alphabetically
        final_order = []
        for cmd in command_order:
            if cmd in actual_commands:
                final_order.append(cmd)
        for cmd in sorted(actual_commands):
            if cmd not in final_order:
                final_order.append(cmd)

        lines = [
            "# CARL Commands File",
            "# ==================",
            f"# Updated: {timestamp}",
            "#",
            "# Format: {COMMAND}_RULE_N=rule text",
            "# Usage: *commandname in prompts",
            f"# COMMAND_ORDER={','.join(final_order)}",
            ""
        ]

        # Write commands in order
        for command in final_order:
            lines.append(f"# *{command.lower()} - Star command")
            lines.append(f"# {'=' * 76}")

            rules = commands[command].get('rules', [])
            for rule in rules:
                idx = rule.get('index', 0)
                text = rule.get('text', '')
                lines.append(f"{command}_RULE_{idx}={text}")

            lines.append("")

        file_path.write_text('\n'.join(lines))

    # =========================================================================
    # Rule CRUD Operations
    # =========================================================================

    def add_rule(self, domain_name: str, rule_text: str, index: int = None) -> dict:
        """
        Add a new rule to a domain.

        Args:
            domain_name: Domain name
            rule_text: Rule text to add
            index: Position to insert (None = append at end)

        Returns:
            {'success': bool, 'index': int, 'error': str | None}
        """
        if not self.exists():
            return {'success': False, 'error': 'CARL installation not found'}

        domain_name = self._sanitize_name(domain_name)
        file_path = self._get_domain_file_path(domain_name)

        if not file_path.exists():
            return {'success': False, 'error': f'Domain file not found: {domain_name}'}

        try:
            rules = self._parse_rules(file_path, domain_name)
            rule_texts = [r['text'] for r in rules]

            # Determine index
            if index is None or index >= len(rule_texts):
                index = len(rule_texts)
                rule_texts.append(rule_text)
            else:
                index = max(0, index)
                rule_texts.insert(index, rule_text)

            self._write_domain_file(file_path, domain_name, rule_texts)
            return {'success': True, 'index': index}
        except Exception as e:
            return {'success': False, 'error': str(e)}

    def update_rule(self, domain_name: str, index: int, rule_text: str) -> dict:
        """
        Update a specific rule in a domain.

        Args:
            domain_name: Domain name
            index: Rule index (0-based)
            rule_text: New rule text

        Returns:
            {'success': bool, 'error': str | None}
        """
        if not self.exists():
            return {'success': False, 'error': 'CARL installation not found'}

        domain_name = self._sanitize_name(domain_name)
        file_path = self._get_domain_file_path(domain_name)

        if not file_path.exists():
            return {'success': False, 'error': f'Domain file not found: {domain_name}'}

        try:
            rules = self._parse_rules(file_path, domain_name)
            rule_texts = [r['text'] for r in rules]

            if index < 0 or index >= len(rule_texts):
                return {'success': False, 'error': f'Invalid rule index: {index}'}

            rule_texts[index] = rule_text
            self._write_domain_file(file_path, domain_name, rule_texts)
            return {'success': True}
        except Exception as e:
            return {'success': False, 'error': str(e)}

    def delete_rule(self, domain_name: str, index: int) -> dict:
        """
        Delete a specific rule from a domain.

        Args:
            domain_name: Domain name
            index: Rule index (0-based)

        Returns:
            {'success': bool, 'error': str | None}
        """
        if not self.exists():
            return {'success': False, 'error': 'CARL installation not found'}

        domain_name = self._sanitize_name(domain_name)
        file_path = self._get_domain_file_path(domain_name)

        if not file_path.exists():
            return {'success': False, 'error': f'Domain file not found: {domain_name}'}

        try:
            rules = self._parse_rules(file_path, domain_name)
            rule_texts = [r['text'] for r in rules]

            if index < 0 or index >= len(rule_texts):
                return {'success': False, 'error': f'Invalid rule index: {index}'}

            rule_texts.pop(index)
            self._write_domain_file(file_path, domain_name, rule_texts)
            return {'success': True}
        except Exception as e:
            return {'success': False, 'error': str(e)}

    def reorder_rules(self, domain_name: str, new_order: list[int]) -> dict:
        """
        Reorder rules in a domain.

        Args:
            domain_name: Domain name
            new_order: List of current indices in desired new order
                       e.g., [2, 0, 1] moves rule 2 to position 0

        Returns:
            {'success': bool, 'error': str | None}
        """
        if not self.exists():
            return {'success': False, 'error': 'CARL installation not found'}

        domain_name = self._sanitize_name(domain_name)
        file_path = self._get_domain_file_path(domain_name)

        if not file_path.exists():
            return {'success': False, 'error': f'Domain file not found: {domain_name}'}

        try:
            rules = self._parse_rules(file_path, domain_name)
            rule_texts = [r['text'] for r in rules]

            # Validate new_order
            if len(new_order) != len(rule_texts):
                return {'success': False, 'error': 'new_order length must match rule count'}
            if set(new_order) != set(range(len(rule_texts))):
                return {'success': False, 'error': 'new_order must contain each index exactly once'}

            # Reorder
            reordered = [rule_texts[i] for i in new_order]
            self._write_domain_file(file_path, domain_name, reordered)
            return {'success': True}
        except Exception as e:
            return {'success': False, 'error': str(e)}

    # =========================================================================
    # Private Helper Methods
    # =========================================================================

    def _sanitize_name(self, name: str) -> str:
        """Sanitize domain name to uppercase alphanumeric only."""
        if not name:
            return ''
        # Remove non-alphanumeric, convert to uppercase
        sanitized = re.sub(r'[^A-Za-z0-9_]', '', name)
        return sanitized.upper()

    def _parse_manifest(self) -> dict:
        """Parse manifest file into key-value dict."""
        entries = {}
        if not self.manifest_path.exists():
            return entries

        content = self.manifest_path.read_text()
        for line in content.splitlines():
            line = line.strip()
            if not line or line.startswith('#'):
                continue
            if '=' in line:
                key, value = line.split('=', 1)
                entries[key.strip()] = value.strip()

        return entries

    def _get_domain_file_path(self, domain_name: str) -> Path:
        """Get the file path for a domain, checking both with and without .env extension."""
        domain_lower = domain_name.lower()
        file_path = self.carl_path / domain_lower
        if not file_path.exists():
            file_path = self.carl_path / f'{domain_lower}.env'
        return file_path

    def _count_rules(self, file_path: Path, domain_name: str) -> int:
        """Count rules in a domain file."""
        rules = self._parse_rules(file_path, domain_name)
        return len(rules)

    def _parse_rules(self, file_path: Path, domain_name: str) -> list[dict]:
        """
        Parse rules from a domain file.

        Returns list of {'index': int, 'text': str}
        """
        rules = []
        if not file_path.exists():
            return rules

        content = file_path.read_text()

        # Handle special formats
        if domain_name == 'CONTEXT':
            return self._parse_context_rules(content)
        elif domain_name == 'COMMANDS':
            return self._parse_commands_rules(content)

        # Standard format: {DOMAIN}_RULE_N=text
        pattern = rf'^{domain_name}_RULE_(\d+)=(.*)$'
        for line in content.splitlines():
            line = line.strip()
            match = re.match(pattern, line, re.IGNORECASE)
            if match:
                index = int(match.group(1))
                text = match.group(2)
                rules.append({'index': index, 'text': text})

        # Sort by index and renumber sequentially
        rules.sort(key=lambda r: r['index'])
        for i, rule in enumerate(rules):
            rule['index'] = i

        return rules

    def _parse_context_rules(self, content: str) -> list[dict]:
        """Parse CONTEXT domain rules (bracket-based format)."""
        rules = []
        # Match {BRACKET}_RULE_{N}=text patterns
        pattern = r'^(\w+)_RULE_(\d+)=(.*)$'
        for line in content.splitlines():
            line = line.strip()
            match = re.match(pattern, line)
            if match:
                bracket = match.group(1)
                index = int(match.group(2))
                text = match.group(3)
                rules.append({
                    'index': len(rules),
                    'text': text,
                    'bracket': bracket,
                    'original_index': index
                })
        return rules

    def _parse_commands_rules(self, content: str) -> list[dict]:
        """Parse COMMANDS domain rules (star command format)."""
        rules = []
        # Match {COMMAND}_RULE_{N}=text patterns
        pattern = r'^(\w+)_RULE_(\d+)=(.*)$'
        for line in content.splitlines():
            line = line.strip()
            match = re.match(pattern, line)
            if match:
                command = match.group(1)
                index = int(match.group(2))
                text = match.group(3)
                rules.append({
                    'index': len(rules),
                    'text': text,
                    'command': command,
                    'original_index': index
                })
        return rules

    def _parse_context_brackets(self, file_path: Path) -> dict:
        """
        Parse CONTEXT file into bracket structure.

        Returns:
            {
                'FRESH': {'enabled': bool, 'rules': [{'index': int, 'text': str}]},
                'MODERATE': {'enabled': bool, 'rules': [...]},
                'DEPLETED': {'enabled': bool, 'rules': [...]}
            }
        """
        brackets = {
            'FRESH': {'enabled': True, 'rules': []},
            'MODERATE': {'enabled': True, 'rules': []},
            'DEPLETED': {'enabled': True, 'rules': []}
        }

        if not file_path.exists():
            return brackets

        content = file_path.read_text()

        # Parse enabled flags: {BRACKET}_RULES=true/false
        enable_pattern = r'^(FRESH|MODERATE|DEPLETED)_RULES\s*=\s*(\w+)'
        for line in content.splitlines():
            match = re.match(enable_pattern, line.strip(), re.IGNORECASE)
            if match:
                bracket = match.group(1).upper()
                value = match.group(2).lower()
                if bracket in brackets:
                    brackets[bracket]['enabled'] = value in ['true', '1', 'yes', 'on']

        # Parse rules: {BRACKET}_RULE_{N}=text
        rule_pattern = r'^(FRESH|MODERATE|DEPLETED)_RULE_(\d+)\s*=\s*(.*)$'
        for line in content.splitlines():
            match = re.match(rule_pattern, line.strip(), re.IGNORECASE)
            if match:
                bracket = match.group(1).upper()
                index = int(match.group(2))
                text = match.group(3)
                if bracket in brackets:
                    brackets[bracket]['rules'].append({'index': index, 'text': text})

        # Sort rules by index within each bracket
        for bracket in brackets:
            brackets[bracket]['rules'].sort(key=lambda r: r['index'])

        return brackets

    def _parse_commands(self, file_path: Path) -> dict:
        """
        Parse COMMANDS file into command structure.

        Returns:
            {
                '_order': ['BRIEF', 'DISCUSS', ...],  # Preserved order
                'BRIEF': {'rules': [{'index': int, 'text': str}]},
                'DISCUSS': {'rules': [...]},
                ...
            }
        """
        commands = {}
        command_order = []

        if not file_path.exists():
            return commands

        content = file_path.read_text()

        # Check for COMMAND_ORDER line
        for line in content.splitlines():
            if line.startswith('# COMMAND_ORDER='):
                order_str = line.replace('# COMMAND_ORDER=', '').strip()
                command_order = [c.strip() for c in order_str.split(',') if c.strip()]
                break

        # Parse rules: {COMMAND}_RULE_{N}=text
        rule_pattern = r'^(\w+)_RULE_(\d+)\s*=\s*(.*)$'
        for line in content.splitlines():
            match = re.match(rule_pattern, line.strip())
            if match:
                command = match.group(1).upper()
                # Skip bracket names (they're from context file format)
                if command in ['FRESH', 'MODERATE', 'DEPLETED']:
                    continue
                index = int(match.group(2))
                text = match.group(3)

                if command not in commands:
                    commands[command] = {'rules': []}
                commands[command]['rules'].append({'index': index, 'text': text})

        # Sort rules by index within each command
        for cmd in commands:
            if cmd != '_order':
                commands[cmd]['rules'].sort(key=lambda r: r['index'])

        # Build final order: specified order first, then remaining alphabetically
        actual_commands = [k for k in commands.keys() if k != '_order']
        final_order = []
        for cmd in command_order:
            if cmd in actual_commands:
                final_order.append(cmd)
        for cmd in sorted(actual_commands):
            if cmd not in final_order:
                final_order.append(cmd)

        commands['_order'] = final_order
        return commands

    def _write_domain_file(self, file_path: Path, domain_name: str, rules: list[str]) -> None:
        """Write a domain file with rules."""
        timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')

        # Build header
        lines = [
            f'# {domain_name} Domain Rules',
            f'# Created/Updated: {timestamp}',
            '#',
            f'# Format: {domain_name}_RULE_N=rule text',
            ''
        ]

        # Add rules
        for i, rule_text in enumerate(rules):
            lines.append(f'{domain_name}_RULE_{i}={rule_text}')

        file_path.write_text('\n'.join(lines) + '\n')

    def _add_manifest_entries(self, domain_name: str, config: dict) -> None:
        """Add new domain entries to manifest."""
        content = self.manifest_path.read_text()
        lines = content.splitlines()

        # Build new entries
        new_entries = []
        new_entries.append('')
        new_entries.append(f'# ============================================================================')
        new_entries.append(f'# {domain_name} - Added by CARL Control Panel')
        new_entries.append(f'# ============================================================================')

        state = 'active' if config.get('state', True) else 'inactive'
        new_entries.append(f'{domain_name}_STATE={state}')

        always_on = 'true' if config.get('always_on', False) else 'false'
        new_entries.append(f'{domain_name}_ALWAYS_ON={always_on}')

        recall = config.get('recall', '')
        if recall:
            new_entries.append(f'{domain_name}_RECALL={recall}')

        exclude = config.get('exclude', '')
        if exclude:
            new_entries.append(f'{domain_name}_EXCLUDE={exclude}')

        # Append to manifest
        lines.extend(new_entries)
        self.manifest_path.write_text('\n'.join(lines) + '\n')

    def _update_manifest_entries(self, domain_name: str, config: dict) -> None:
        """Update existing domain entries in manifest."""
        content = self.manifest_path.read_text()
        lines = content.splitlines()

        # Keys to potentially update
        updates = {}
        if 'state' in config:
            updates[f'{domain_name}_STATE'] = 'active' if config['state'] else 'inactive'
        if 'always_on' in config:
            updates[f'{domain_name}_ALWAYS_ON'] = 'true' if config['always_on'] else 'false'
        if 'recall' in config:
            updates[f'{domain_name}_RECALL'] = config['recall']
        if 'exclude' in config:
            updates[f'{domain_name}_EXCLUDE'] = config['exclude']

        found_keys = set()

        # Update existing lines
        for i, line in enumerate(lines):
            stripped = line.strip()
            if stripped.startswith('#') or not stripped:
                continue
            if '=' in stripped:
                key = stripped.split('=')[0].strip()
                if key in updates:
                    lines[i] = f'{key}={updates[key]}'
                    found_keys.add(key)

        # Add any missing keys
        for key, value in updates.items():
            if key not in found_keys:
                lines.append(f'{key}={value}')

        self.manifest_path.write_text('\n'.join(lines) + '\n')

    def _remove_manifest_entries(self, domain_name: str) -> None:
        """Remove all domain entries from manifest."""
        content = self.manifest_path.read_text()
        lines = content.splitlines()

        prefix = f'{domain_name}_'
        new_lines = []
        skip_next_blank = False

        for line in lines:
            stripped = line.strip()

            # Check if this line is a domain entry
            if stripped.startswith(prefix):
                skip_next_blank = True
                continue

            # Check for section header comment
            if stripped.startswith('#') and domain_name in stripped:
                # Could be a section header - check for pattern
                if '====' in stripped or f'# {domain_name}' in stripped:
                    skip_next_blank = True
                    continue

            # Skip blank lines immediately after removed content
            if skip_next_blank and not stripped:
                skip_next_blank = False
                continue

            skip_next_blank = False
            new_lines.append(line)

        self.manifest_path.write_text('\n'.join(new_lines) + '\n')


# =========================================================================
# Module-level convenience functions (for API routes)
# =========================================================================

def list_domains(carl_path: str) -> dict:
    """List all domains with summary info."""
    manager = DomainManager(Path(carl_path))
    return manager.list_domains()


def get_domain(carl_path: str, name: str) -> dict:
    """Get domain detail + rules."""
    manager = DomainManager(Path(carl_path))
    return manager.get_domain(name)


def create_domain(carl_path: str, name: str, config: dict) -> dict:
    """Create new domain with file + manifest entries."""
    manager = DomainManager(Path(carl_path))
    return manager.create_domain(name, config)


def update_domain(carl_path: str, name: str, config: dict) -> dict:
    """Update domain config/rules."""
    manager = DomainManager(Path(carl_path))
    return manager.update_domain(name, config)


def delete_domain(carl_path: str, name: str) -> dict:
    """Delete domain (file + manifest entries)."""
    manager = DomainManager(Path(carl_path))
    return manager.delete_domain(name)


def add_rule(carl_path: str, domain_name: str, rule_text: str, index: int = None) -> dict:
    """Add new rule to domain."""
    manager = DomainManager(Path(carl_path))
    return manager.add_rule(domain_name, rule_text, index)


def update_rule(carl_path: str, domain_name: str, index: int, rule_text: str) -> dict:
    """Update specific rule."""
    manager = DomainManager(Path(carl_path))
    return manager.update_rule(domain_name, index, rule_text)


def delete_rule(carl_path: str, domain_name: str, index: int) -> dict:
    """Delete specific rule."""
    manager = DomainManager(Path(carl_path))
    return manager.delete_rule(domain_name, index)


def reorder_rules(carl_path: str, domain_name: str, new_order: list[int]) -> dict:
    """Reorder rules in domain."""
    manager = DomainManager(Path(carl_path))
    return manager.reorder_rules(domain_name, new_order)


def reorder_domains(carl_path: str, order: list[str]) -> dict:
    """Reorder domains in the list."""
    manager = DomainManager(Path(carl_path))
    return manager.reorder_domains(order)


# Special domain operations
def update_context_bracket(carl_path: str, bracket: str, data: dict) -> dict:
    """Update a CONTEXT bracket."""
    manager = DomainManager(Path(carl_path))
    return manager.update_context_bracket(bracket, data)


def update_command(carl_path: str, command: str, data: dict) -> dict:
    """Update a star command."""
    manager = DomainManager(Path(carl_path))
    return manager.update_command(command, data)


def delete_command(carl_path: str, command: str) -> dict:
    """Delete a star command."""
    manager = DomainManager(Path(carl_path))
    return manager.delete_command(command)


def reorder_commands(carl_path: str, order: list[str]) -> dict:
    """Reorder star commands."""
    manager = DomainManager(Path(carl_path))
    return manager.reorder_commands(order)
