#!/usr/bin/env python3
"""
Merge developer base with senior delta to produce senior agent.

Delta commands:
- ## REPLACE: <section_marker>  - Replace section matching marker
- ## REMOVE: <section_marker>   - Remove section matching marker
- ## ADD_AFTER: <section_marker> - Insert content after section
- ## ADD_BEFORE: <section_marker> - Insert content before section
- ## MODIFY: <section_marker>   - Append modification notes to section

Section markers match headers in the base file (without the ## prefix).
Special markers:
- FRONTMATTER : The YAML frontmatter block (---)

Usage:
    python merge_agent_delta.py <base_file> <delta_file> <output_file>
"""

import re
import sys
from pathlib import Path
from typing import Optional


# Header to add to generated files
GENERATED_FILE_HEADER = """\
<!-- ⚠️  AUTO-GENERATED FILE - DO NOT EDIT DIRECTLY ⚠️

     This file is generated by scripts/build-agent-files.sh

     To modify this file:
     1. Edit agents/_sources/developer.base.md (for shared content)
     2. Edit agents/_sources/senior.delta.md (for senior-specific content)
     3. Run: ./scripts/build-agent-files.sh

     Direct edits to this file will be overwritten on next build!
-->

"""


def parse_delta_file(delta_content: str) -> dict:
    """Parse delta file into operations."""
    operations = {
        'replace': {},      # marker -> new_content
        'remove': set(),    # set of markers to remove
        'add_after': {},    # marker -> content to add
        'add_before': {},   # marker -> content to add
        'modify': {},       # marker -> modification content
    }

    current_op = None
    current_marker = None
    current_content = []

    lines = delta_content.split('\n')
    i = 0

    while i < len(lines):
        line = lines[i]

        # Skip single-line comments (starting with '# ') and empty lines outside operations
        # Note: We must NOT skip '## ' lines as they are our operation markers
        if current_op is None:
            if (line.startswith('# ') and not line.startswith('## ')) or line.strip() == '':
                i += 1
                continue

        # Check for operation markers
        replace_match = re.match(r'^## REPLACE: (.+)$', line)
        remove_match = re.match(r'^## REMOVE: (.+)$', line)
        add_after_match = re.match(r'^## ADD_AFTER: (.+)$', line)
        add_before_match = re.match(r'^## ADD_BEFORE: (.+)$', line)
        modify_match = re.match(r'^## MODIFY: (.+)$', line)
        end_match = re.match(r'^## END_(REPLACE|ADD|MODIFY)$', line)

        if replace_match:
            # Save previous operation if any
            if current_op and current_marker:
                save_operation(operations, current_op, current_marker, current_content)
            current_op = 'replace'
            current_marker = replace_match.group(1).strip()
            current_content = []
            i += 1
            continue

        elif remove_match:
            marker = remove_match.group(1).strip()
            operations['remove'].add(marker)
            i += 1
            continue

        elif add_after_match:
            if current_op and current_marker:
                save_operation(operations, current_op, current_marker, current_content)
            current_op = 'add_after'
            current_marker = add_after_match.group(1).strip()
            current_content = []
            i += 1
            continue

        elif add_before_match:
            if current_op and current_marker:
                save_operation(operations, current_op, current_marker, current_content)
            current_op = 'add_before'
            current_marker = add_before_match.group(1).strip()
            current_content = []
            i += 1
            continue

        elif modify_match:
            if current_op and current_marker:
                save_operation(operations, current_op, current_marker, current_content)
            current_op = 'modify'
            current_marker = modify_match.group(1).strip()
            current_content = []
            i += 1
            continue

        elif end_match:
            if current_op and current_marker:
                save_operation(operations, current_op, current_marker, current_content)
            current_op = None
            current_marker = None
            current_content = []
            i += 1
            continue

        # Accumulate content for current operation
        if current_op is not None:
            current_content.append(line)

        i += 1

    # Save any remaining operation
    if current_op and current_marker:
        save_operation(operations, current_op, current_marker, current_content)

    return operations


def save_operation(operations: dict, op_type: str, marker: str, content: list):
    """Save operation content, trimming leading/trailing blank lines."""
    # Remove leading blank lines
    while content and content[0].strip() == '':
        content.pop(0)
    # Remove trailing blank lines
    while content and content[-1].strip() == '':
        content.pop()

    content_str = '\n'.join(content)

    if op_type == 'replace':
        operations['replace'][marker] = content_str
    elif op_type == 'add_after':
        operations['add_after'][marker] = content_str
    elif op_type == 'add_before':
        operations['add_before'][marker] = content_str
    elif op_type == 'modify':
        operations['modify'][marker] = content_str


def is_inside_code_fence(lines: list, line_idx: int) -> bool:
    """Check if a line is inside a fenced code block.

    Tracks opening/closing of ``` fences from start of file to line_idx.
    """
    in_fence = False
    for i in range(line_idx):
        line = lines[i].strip()
        # Check for code fence (``` or ```language)
        if line.startswith('```'):
            in_fence = not in_fence
    return in_fence


def find_section_bounds(lines: list, marker: str) -> tuple[Optional[int], Optional[int]]:
    """Find the start and end line indices for a section.

    Returns (start_idx, end_idx) where end_idx is the line AFTER the section.
    Ignores headers inside fenced code blocks.
    """
    # Special case for frontmatter
    if marker == 'FRONTMATTER':
        if lines[0].strip() == '---':
            for i in range(1, len(lines)):
                if lines[i].strip() == '---':
                    return 0, i + 1
        return None, None

    # Special case for INTRO - the main header (# Title) and content up to first ## header
    # This allows replacing just the intro without replacing the entire file
    if marker == 'INTRO':
        start_idx = None
        # Find the first level-1 header (after frontmatter)
        for i, line in enumerate(lines):
            if is_inside_code_fence(lines, i):
                continue
            header_match = re.match(r'^#\s+(.+)$', line)
            if header_match:
                start_idx = i
                break
        if start_idx is None:
            return None, None
        # Find end at first ## header (any level 2+ header)
        end_idx = len(lines)
        for i in range(start_idx + 1, len(lines)):
            if is_inside_code_fence(lines, i):
                continue
            if re.match(r'^#{2,6}\s+', lines[i]):
                end_idx = i
                break
        return start_idx, end_idx

    # Find header matching marker - match the text after #'s
    # The marker is just the text, we need to find "# <marker>" or "## <marker>" etc.
    start_idx = None
    header_level = None

    for i, line in enumerate(lines):
        # Skip lines inside code fences
        if is_inside_code_fence(lines, i):
            continue

        # Check if this line is a header that ends with our marker
        header_match = re.match(r'^(#{1,6})\s+(.+)$', line)
        if header_match:
            header_text = header_match.group(2).strip()
            if header_text == marker:
                start_idx = i
                header_level = len(header_match.group(1))
                break

    if start_idx is None:
        return None, None

    # Find end of section (next header of same or higher level, not in code fence)
    end_idx = len(lines)
    for i in range(start_idx + 1, len(lines)):
        # Skip lines inside code fences
        if is_inside_code_fence(lines, i):
            continue

        line = lines[i]
        header_match = re.match(r'^(#{1,6})\s+', line)
        if header_match:
            level = len(header_match.group(1))
            if level <= header_level:
                end_idx = i
                break

    return start_idx, end_idx


def apply_operations(base_content: str, operations: dict) -> str:
    """Apply delta operations to base content."""
    lines = base_content.split('\n')

    # Track modifications to apply
    # Each tuple: (operation_type, start_line, end_line, content)
    all_ops = []

    # Process REPLACE operations
    for marker, new_content in operations['replace'].items():
        start, end = find_section_bounds(lines, marker)
        if start is not None:
            all_ops.append(('replace', start, end, new_content))
            print(f"  REPLACE: '{marker}' (lines {start}-{end})")
        else:
            print(f"  Warning: Could not find section for REPLACE: {marker}", file=sys.stderr)

    # Process REMOVE operations
    for marker in operations['remove']:
        start, end = find_section_bounds(lines, marker)
        if start is not None:
            all_ops.append(('remove', start, end, None))
            print(f"  REMOVE: '{marker}' (lines {start}-{end})")
        else:
            print(f"  Warning: Could not find section for REMOVE: {marker}", file=sys.stderr)

    # Process ADD_AFTER operations - insert at section end
    for marker, content in operations['add_after'].items():
        start, end = find_section_bounds(lines, marker)
        if end is not None:
            # Insert position is the end of the section
            all_ops.append(('add_after', end, end, content))
            print(f"  ADD_AFTER: '{marker}' (insert at line {end})")
        else:
            print(f"  Warning: Could not find section for ADD_AFTER: {marker}", file=sys.stderr)

    # Process ADD_BEFORE operations - insert at section start
    for marker, content in operations['add_before'].items():
        start, end = find_section_bounds(lines, marker)
        if start is not None:
            # Insert position is the start of the section
            all_ops.append(('add_before', start, start, content))
            print(f"  ADD_BEFORE: '{marker}' (insert at line {start})")
        else:
            print(f"  Warning: Could not find section for ADD_BEFORE: {marker}", file=sys.stderr)

    # Process MODIFY operations (append to end of section)
    for marker, content in operations['modify'].items():
        start, end = find_section_bounds(lines, marker)
        if end is not None:
            # Insert before the next section (at end of current section)
            all_ops.append(('modify', end, end, content))
            print(f"  MODIFY: '{marker}' (append at line {end})")
        else:
            print(f"  Warning: Could not find section for MODIFY: {marker}", file=sys.stderr)

    # Sort by position (descending) so we process from end to start
    # This preserves line numbers as we make modifications
    all_ops.sort(key=lambda x: x[1], reverse=True)

    # Apply operations
    for op_type, start, end, content in all_ops:
        if op_type == 'replace':
            # Replace lines[start:end] with new content
            lines[start:end] = content.split('\n')
        elif op_type == 'remove':
            # Remove section and any trailing blank lines
            remove_end = end
            while remove_end < len(lines) and lines[remove_end].strip() == '':
                remove_end += 1
            del lines[start:remove_end]
        elif op_type == 'add_after':
            # Insert content after the section (at position 'start' which is actually section end)
            insert_lines = ['', ''] + content.split('\n')
            lines[start:start] = insert_lines
        elif op_type == 'add_before':
            # Insert content before the section
            insert_lines = content.split('\n') + ['', '']
            lines[start:start] = insert_lines
        elif op_type == 'modify':
            # Append content at end of section (before next section)
            insert_lines = ['', ''] + content.split('\n')
            lines[start:start] = insert_lines

    return '\n'.join(lines)


def main():
    if len(sys.argv) != 4:
        print(f"Usage: {sys.argv[0]} <base_file> <delta_file> <output_file>")
        sys.exit(1)

    base_path = Path(sys.argv[1])
    delta_path = Path(sys.argv[2])
    output_path = Path(sys.argv[3])

    if not base_path.exists():
        print(f"Error: Base file not found: {base_path}")
        sys.exit(1)

    if not delta_path.exists():
        print(f"Error: Delta file not found: {delta_path}")
        sys.exit(1)

    # Read files
    base_content = base_path.read_text()
    delta_content = delta_path.read_text()

    # Parse delta
    print("Parsing delta file...")
    operations = parse_delta_file(delta_content)

    # Show what we found
    print(f"  Found {len(operations['replace'])} REPLACE operations")
    print(f"  Found {len(operations['remove'])} REMOVE operations")
    print(f"  Found {len(operations['add_after'])} ADD_AFTER operations")
    print(f"  Found {len(operations['add_before'])} ADD_BEFORE operations")
    print(f"  Found {len(operations['modify'])} MODIFY operations")

    # Apply operations
    print("\nApplying operations...")
    result = apply_operations(base_content, operations)

    # Add generated file header for non-base files (AFTER frontmatter, not before)
    # Frontmatter must be at line 1 for YAML parsing to work
    if 'senior' in output_path.name.lower():
        # Extract frontmatter if present
        if result.startswith('---'):
            # Find closing ---
            second_delim = result.find('---', 3)
            if second_delim != -1:
                # Find end of frontmatter line
                end_of_frontmatter = result.find('\n', second_delim) + 1
                frontmatter = result[:end_of_frontmatter]
                rest = result[end_of_frontmatter:]
                result = frontmatter + '\n' + GENERATED_FILE_HEADER + rest
            else:
                # No closing ---, prepend header (will break parsing anyway)
                result = GENERATED_FILE_HEADER + result
        else:
            # No frontmatter, prepend header
            result = GENERATED_FILE_HEADER + result

    # Write output
    output_path.parent.mkdir(parents=True, exist_ok=True)
    output_path.write_text(result)

    print(f"\nGenerated: {output_path}")


if __name__ == '__main__':
    main()
