"""Delete command: Remove downloaded LLM instruction files."""

import shutil
from pathlib import Path

import typer
from typing_extensions import Annotated

from llm_ide_rules.commands.download import INSTRUCTION_TYPES, DEFAULT_TYPES
from llm_ide_rules.constants import header_to_filename
from llm_ide_rules.log import log
from llm_ide_rules.markdown_parser import parse_sections


def get_generated_files(target_dir: Path) -> set[Path]:
    """Identify files that would be generated from local instruction files."""
    generated = set()

    # Check instructions.md
    instructions_path = target_dir / "instructions.md"
    if instructions_path.exists():
        try:
            general, sections = parse_sections(instructions_path.read_text())

            # If general instructions exist, these files are generated
            if any(line.strip() for line in general):
                generated.add(target_dir / ".cursor/rules/general.mdc")
                generated.add(target_dir / ".github/copilot-instructions.md")
                generated.add(target_dir / "CLAUDE.md")
                generated.add(target_dir / "AGENTS.md")

            # If any sections exist, root docs are definitely generated
            if sections:
                generated.add(target_dir / "CLAUDE.md")
                generated.add(target_dir / "AGENTS.md")

            # Section specific files
            from llm_ide_rules.utils import resolve_target_dir

            for header, section_data in sections.items():
                filename = header_to_filename(header)
                generated.add(target_dir / f".cursor/rules/{filename}.mdc")
                generated.add(
                    target_dir / f".github/instructions/{filename}.instructions.md"
                )

                # Add subdirectory CLAUDE.md and AGENTS.md for sections with ** glob patterns
                glob_pattern = section_data.glob_pattern
                section_target_dir = resolve_target_dir(target_dir, glob_pattern)

                if section_target_dir != target_dir:
                    generated.add(section_target_dir / "CLAUDE.md")
                    generated.add(section_target_dir / "AGENTS.md")

        except Exception as e:
            log.warning("failed to parse instructions.md", error=str(e))

    # Check commands.md
    commands_path = target_dir / "commands.md"
    if commands_path.exists():
        try:
            _, sections = parse_sections(commands_path.read_text())
            for header in sections:
                filename = header_to_filename(header)
                generated.add(target_dir / f".cursor/commands/{filename}.md")
                generated.add(target_dir / f".github/prompts/{filename}.prompt.md")
                generated.add(target_dir / f".gemini/commands/{filename}.toml")
                generated.add(target_dir / f".claude/commands/{filename}.md")
                generated.add(target_dir / f".opencode/commands/{filename}.md")
        except Exception as e:
            log.warning("failed to parse commands.md", error=str(e))

    return {p.resolve() for p in generated}


def find_files_to_delete(
    instruction_types: list[str], target_dir: Path
) -> tuple[list[Path], list[Path]]:
    """Find all files and directories that would be deleted.

    Returns:
        Tuple of (directories, files) to delete
    """
    dirs_to_delete = []
    files_to_delete = []

    for inst_type in instruction_types:
        if inst_type not in INSTRUCTION_TYPES:
            log.warning("unknown instruction type", type=inst_type)
            continue

        config = INSTRUCTION_TYPES[inst_type]

        for dir_name in config["directories"]:
            dir_path = target_dir / dir_name
            if dir_path.exists() and dir_path.is_dir():
                dirs_to_delete.append(dir_path)

        for file_name in config["files"]:
            file_path = target_dir / file_name
            if file_path.exists() and file_path.is_file():
                files_to_delete.append(file_path)

        for file_name in config.get("generated_files", []):
            file_path = target_dir / file_name
            if file_path.exists() and file_path.is_file():
                files_to_delete.append(file_path)

        for file_pattern in config.get("recursive_files", []):
            matching_files = list(target_dir.rglob(file_pattern))
            files_to_delete.extend([f for f in matching_files if f.is_file()])

    # Deduplicate files to delete while preserving order
    files_to_delete = list(dict.fromkeys(files_to_delete))

    return dirs_to_delete, files_to_delete


def delete_main(
    instruction_types: Annotated[
        list[str] | None,
        typer.Argument(
            help="Types of instructions to delete (cursor, github, gemini, claude, opencode, agents). Deletes everything by default."
        ),
    ] = None,
    target_dir: Annotated[
        str, typer.Option("--target", "-t", help="Target directory to delete from")
    ] = ".",
    everything: Annotated[
        bool,
        typer.Option(
            "--everything",
            help="Delete all instruction files, not just those generated from local sources.",
        ),
    ] = False,
    yes: Annotated[
        bool,
        typer.Option(
            "--yes", "-y", help="Skip confirmation prompt and delete immediately"
        ),
    ] = False,
):
    """Remove downloaded LLM instruction files.

    This command removes files and directories that were downloaded by the 'download' command
    or generated by the 'explode' command.

    By default, it ONLY deletes files that correspond to your local 'instructions.md' and
    'commands.md' files. This prevents accidental deletion of manually created files.
    Use --everything to delete all standard instruction files and directories.

    Examples:

    \b
    # Delete only generated files (safest, default)
    llm_ide_rules delete

    \b
    # Delete ALL instruction files (including manual ones)
    llm_ide_rules delete --everything

    \b
    # Delete only Cursor and Gemini files (but only if generated)
    llm_ide_rules delete cursor gemini

    \b
    # Delete without confirmation prompt
    llm_ide_rules delete --yes

    \b
    # Delete from a specific directory
    llm_ide_rules delete --target ./my-project
    """
    if not instruction_types:
        instruction_types = DEFAULT_TYPES

    invalid_types = [t for t in instruction_types if t not in INSTRUCTION_TYPES]
    if invalid_types:
        log.error(
            "invalid instruction types",
            invalid_types=invalid_types,
            valid_types=list(INSTRUCTION_TYPES.keys()),
        )
        raise typer.Exit(1)

    target_path = Path(target_dir).resolve()

    if not target_path.exists():
        log.error("target directory does not exist", target_dir=str(target_path))
        error_msg = f"Target directory does not exist: {target_path}"
        typer.echo(typer.style(error_msg, fg=typer.colors.RED), err=True)
        raise typer.Exit(1)

    log.info(
        "finding files to delete",
        instruction_types=instruction_types,
        target_dir=str(target_path),
    )

    dirs_to_delete, files_to_delete = find_files_to_delete(
        instruction_types, target_path
    )

    skipped_files = []

    if not everything:
        log.info("filtering files to delete based on local sources")
        generated_files = get_generated_files(target_path)

        # Expand directories to files for granular filtering
        expanded_files = []
        for d in dirs_to_delete:
            expanded_files.extend([f for f in d.rglob("*") if f.is_file()])

        all_candidates = files_to_delete + expanded_files

        # Filter: keep only files that are in the generated set
        # We compare resolved paths to be safe
        files_to_delete = [f for f in all_candidates if f.resolve() in generated_files]

        # Identify skipped files (candidates that were NOT in generated set)
        skipped_files = [
            f for f in all_candidates if f.resolve() not in generated_files
        ]

        # We are no longer deleting whole directories in safe mode
        dirs_to_delete = []

    if not dirs_to_delete and not files_to_delete:
        log.info("no files found to delete")
        typer.echo("No matching instruction files found to delete.")
        if skipped_files:
            typer.echo(
                f"\n{len(skipped_files)} files were skipped because they don't match local instructions/commands."
            )
            typer.echo("Use --everything to delete them.")
        return

    typer.echo("\nThe following files and directories will be deleted:\n")

    if dirs_to_delete:
        typer.echo("Directories:")
        for dir_path in sorted(dirs_to_delete):
            relative_path = dir_path.relative_to(target_path)
            typer.echo(f"  - {relative_path}/")

    if files_to_delete:
        typer.echo("\nFiles:")
        for file_path in sorted(files_to_delete):
            relative_path = file_path.relative_to(target_path)
            typer.echo(f"  - {relative_path}")

    total_items = len(dirs_to_delete) + len(files_to_delete)
    typer.echo(f"\nTotal: {total_items} items")

    if skipped_files:
        typer.echo(
            f"\n(Note: {len(skipped_files)} other files will be preserved. Use --everything to delete them)"
        )

    if not yes:
        typer.echo()
        confirm = typer.confirm("Are you sure you want to delete these files?")
        if not confirm:
            log.info("deletion cancelled by user")
            typer.echo("Deletion cancelled.")
            raise typer.Exit(0)

    deleted_count = 0

    for dir_path in dirs_to_delete:
        try:
            log.info("deleting directory", path=str(dir_path))
            shutil.rmtree(dir_path)
            deleted_count += 1
        except Exception as e:
            log.error("failed to delete directory", path=str(dir_path), error=str(e))
            typer.echo(f"Error deleting {dir_path}: {e}", err=True)

    for file_path in files_to_delete:
        try:
            log.info("deleting file", path=str(file_path))
            file_path.unlink()
            deleted_count += 1
        except Exception as e:
            log.error("failed to delete file", path=str(file_path), error=str(e))
            typer.echo(f"Error deleting {file_path}: {e}", err=True)

    success_msg = f"Successfully deleted {deleted_count} of {total_items} items."
    typer.echo(typer.style(success_msg, fg=typer.colors.GREEN))
