"""Stale links transformer - removes markdown links to non-copied files."""

import re
from pathlib import Path
from typing import Any, Dict, List
from urllib.parse import urlparse, unquote, quote

from ..base import Transformer, read_text_safe


class StaleLinksTransformer(Transformer):
    """Transformer that removes or converts markdown links to files not in the output.

    After filtering and copying, some internal links may point to files that
    were intentionally excluded.  This transformer detects those *stale* links
    and either removes them entirely or converts them to plain text.

    Config keys:
        remove_stale_links (bool): When ``True`` (default), stale links are
                                    deleted from the document.
        convert_to_text    (bool): When ``True``, the link is replaced with its
                                    visible text instead of being deleted.
                                    Takes precedence over ``remove_stale_links``
                                    when both are ``True``.

    Example YAML::

        - name: stale_links
          type: publishmd.transformers.stale_links_transformer.StaleLinksTransformer
          config:
            remove_stale_links: true
            convert_to_text: true
    """

    def __init__(self, config: Dict[str, Any]):
        """Initialize the stale links transformer."""
        super().__init__(config)
        self.remove_stale_links = config.get("remove_stale_links", True)
        self.convert_to_text = config.get("convert_to_text", False)

    def transform(self, file_path: Path, copied_files: List[Path]) -> None:
        """
        Remove or convert stale markdown links in a file.

        Args:
            file_path: Path to the file to transform
            copied_files: List of all copied files for reference
        """
        if not file_path.exists():
            return

        try:
            content = read_text_safe(file_path)
            original_content = content

            # Find all markdown links: [text](path) but exclude image links ![text](path)
            # Use negative lookbehind to exclude links preceded by !
            link_pattern = r"(?<!!)\[([^\]]+)\]\(([^)]+)\)"

            def replace_stale_link(match):
                link_text = match.group(1)
                link_path = match.group(2)

                # Skip URLs - they're not stale local links
                if self._is_url(link_path):
                    return match.group(0)

                # Check if the linked file exists in copied files
                target_path = self._resolve_link_path(
                    link_path, file_path, copied_files
                )

                if target_path and self._is_copied_file(target_path, copied_files):
                    # Link is valid - check if we need to update the extension
                    updated_link_path = self._get_updated_link_path(
                        link_path, target_path, copied_files
                    )
                    if updated_link_path != link_path:
                        return f"[{link_text}]({updated_link_path})"
                    else:
                        return match.group(0)
                else:
                    # Link is stale
                    if self.convert_to_text:
                        return link_text
                    elif self.remove_stale_links:
                        return ""
                    else:
                        return match.group(0)

            content = re.sub(link_pattern, replace_stale_link, content)

            # Clean up excessive spacing (3 or more spaces) and excessive newlines
            content = re.sub(r"   +", " ", content)  # 3 or more spaces -> 1 space
            content = re.sub(r"\n\n\n+", "\n\n", content)

            # Only write if content changed
            if content != original_content:
                file_path.write_text(content, encoding="utf-8")

        except IOError:
            pass

    def _is_url(self, path_str: str) -> bool:
        """Check if a path string is a URL."""
        parsed = urlparse(path_str)
        return bool(parsed.scheme and parsed.netloc)

    def _resolve_link_path(
        self, link_path: str, current_file: Path, copied_files: List[Path]
    ) -> Path:
        """
        Resolve a link path to an actual file path.

        Args:
            link_path: The link path from the markdown
            current_file: Current file being processed
            copied_files: List of copied files

        Returns:
            Resolved path, or None if not resolvable
        """
        # Remove URL fragments and query parameters
        clean_path = link_path.split("#")[0].split("?")[0]

        # URL decode the path to handle spaces and special characters
        decoded_path = unquote(clean_path)

        # Try relative to current file
        try:
            if decoded_path.startswith("/"):
                # Absolute path - try relative to project root
                # Use the first emitted file's parent as the root directory
                if copied_files:
                    root_dir = copied_files[0].parent
                    while root_dir.parent != root_dir:  # Find project root
                        potential_root = root_dir.parent
                        if any(
                            potential_root.glob("*.qmd") or potential_root.glob("*.md")
                        ):
                            root_dir = potential_root
                        else:
                            break
                    resolved_path = (root_dir / decoded_path.lstrip("/")).resolve()
                else:
                    resolved_path = Path(decoded_path).resolve()
            else:
                # Relative path
                resolved_path = (current_file.parent / decoded_path).resolve()

            # First try exact path
            if resolved_path.exists():
                return resolved_path

            # If .md/.markdown file doesn't exist, try corresponding .qmd file
            if resolved_path.suffix in [".md", ".markdown"]:
                qmd_path = resolved_path.with_suffix(".qmd")
                if qmd_path.exists():
                    return qmd_path

        except (ValueError, OSError):
            pass

        return None

    def _is_copied_file(self, target_path: Path, copied_files: List[Path]) -> bool:
        """
        Check if a target path corresponds to an emitted file.

        Args:
            target_path: Path to check
            copied_files: List of copied files

        Returns:
            True if the target path is in the copied files
        """
        try:
            target_resolved = target_path.resolve()
            for emitted_file in copied_files:
                emitted_resolved = emitted_file.resolve()

                # Exact match
                if emitted_resolved == target_resolved:
                    return True

                # Check if target .md/.markdown file has corresponding .qmd emitted file
                if (
                    target_path.suffix in [".md", ".markdown"]
                    and emitted_file.suffix == ".qmd"
                ):
                    # Compare paths without extension
                    target_without_ext = target_resolved.with_suffix("")
                    emitted_without_ext = emitted_resolved.with_suffix("")
                    if target_without_ext == emitted_without_ext:
                        return True

        except OSError:
            pass

        return False

    def _get_updated_link_path(
        self, original_link_path: str, target_path: Path, copied_files: List[Path]
    ) -> str:
        """
        Get the updated link path if the target file was emitted with a different extension.

        Args:
            original_link_path: Original link path from the markdown
            target_path: Resolved target path
            copied_files: List of copied files

        Returns:
            Updated link path, properly URL-encoded
        """
        # Decode the original path to check the extension
        decoded_original = unquote(original_link_path)
        original_path = Path(decoded_original)

        if original_path.suffix not in [".md", ".markdown"]:
            return original_link_path

        try:
            target_resolved = target_path.resolve()

            # Find the corresponding emitted file
            for emitted_file in copied_files:
                emitted_resolved = emitted_file.resolve()

                # Check if the resolved target matches an emitted .qmd file
                if (
                    emitted_file.suffix == ".qmd"
                    and target_resolved == emitted_resolved
                ):

                    # Update the path to use .qmd extension and re-encode
                    updated_decoded = decoded_original.replace(
                        original_path.suffix, ".qmd"
                    )
                    # URL encode the updated path, preserving directory separators
                    path_parts = updated_decoded.split("/")
                    encoded_parts = [quote(part, safe="") for part in path_parts]
                    return "/".join(encoded_parts)

        except OSError:
            pass

        return original_link_path
