"""Composable output filters for CLI display.

This module provides a protocol-based filter system for processing
tool outputs before display. Filters are composable via FilterPipeline.

Example:
    pipeline = FilterPipeline([
        RegexRemoveFilter(re.compile(r"<secret>.*?</secret>", re.DOTALL)),
        LineFilter(lambda line: "password" not in line.lower()),
    ])
    cleaned = pipeline.apply(raw_output)
"""

from __future__ import annotations

import re
from dataclasses import dataclass
from typing import Callable, Protocol, runtime_checkable


@runtime_checkable
class OutputFilter(Protocol):
    """Protocol for output filters.

    Each filter transforms text and returns the result.
    Filters should be immutable and side-effect free.
    """

    def apply(self, text: str) -> str:
        """Apply the filter and return transformed text."""
        ...


@dataclass(frozen=True)
class RegexRemoveFilter:
    """Remove all content matching a regex pattern.

    Args:
        pattern: Compiled regex pattern to match and remove.

    Example:
        filter = RegexRemoveFilter(re.compile(r"<tag>.*?</tag>", re.DOTALL))
        result = filter.apply("before <tag>secret</tag> after")
        # result: "before  after"
    """

    pattern: re.Pattern[str]

    def apply(self, text: str) -> str:
        return self.pattern.sub("", text)


@dataclass(frozen=True)
class RegexSubstituteFilter:
    """Replace content matching a regex pattern with a replacement string.

    Args:
        pattern: Compiled regex pattern to match.
        replacement: String to replace matches with.
    """

    pattern: re.Pattern[str]
    replacement: str = ""

    def apply(self, text: str) -> str:
        return self.pattern.sub(self.replacement, text)


@dataclass(frozen=True)
class LineFilter:
    """Filter lines based on a predicate function.

    Args:
        predicate: Function that returns True for lines to KEEP.

    Example:
        filter = LineFilter(lambda line: "secret" not in line)
        result = filter.apply("keep this\\nremove secret\\nkeep this too")
        # result: "keep this\\nkeep this too"
    """

    predicate: Callable[[str], bool]

    def apply(self, text: str) -> str:
        return "\n".join(line for line in text.split("\n") if self.predicate(line))


@dataclass(frozen=True)
class LinePrefixStripFilter:
    """Remove a prefix pattern from each line.

    Args:
        pattern: Compiled regex pattern to strip from line starts.

    Example:
        filter = LinePrefixStripFilter(re.compile(r"^\\s*\\d+→"))
        result = filter.apply("  42→content here")
        # result: "content here"
    """

    pattern: re.Pattern[str]

    def apply(self, text: str) -> str:
        return "\n".join(self.pattern.sub("", line) for line in text.split("\n"))


@dataclass(frozen=True)
class BlockRemoveFilter:
    """Remove content between start and end markers using a state machine.

    This handles cases where markers might span multiple lines or
    appear mid-line. More robust than simple regex for nested/malformed content.

    Args:
        start_marker: String that marks the beginning of content to remove.
        end_marker: String that marks the end of content to remove.

    Example:
        filter = BlockRemoveFilter("<remove>", "</remove>")
        result = filter.apply("keep <remove>hidden</remove> this")
        # result: "keep  this"
    """

    start_marker: str
    end_marker: str

    def apply(self, text: str) -> str:
        lines = []
        in_block = False

        for line in text.split("\n"):
            if self.start_marker in line and self.end_marker in line:
                # Both markers on same line - remove just the block
                start_idx = line.find(self.start_marker)
                end_idx = line.find(self.end_marker) + len(self.end_marker)
                line = line[:start_idx] + line[end_idx:]
                if line.strip():
                    lines.append(line)
            elif self.start_marker in line:
                # Block starts - keep content before marker
                before = line[: line.find(self.start_marker)]
                if before.strip():
                    lines.append(before)
                in_block = True
            elif self.end_marker in line:
                # Block ends - keep content after marker
                after = line[line.find(self.end_marker) + len(self.end_marker) :]
                if after.strip():
                    lines.append(after)
                in_block = False
            elif not in_block:
                lines.append(line)

        return "\n".join(lines)


class FilterPipeline:
    """Compose multiple filters into a single processing pipeline.

    Filters are applied in order. The pipeline itself implements
    the OutputFilter protocol, so pipelines can be nested.

    Example:
        pipeline = FilterPipeline([
            RegexRemoveFilter(PATTERN_A),
            LineFilter(predicate),
            RegexRemoveFilter(PATTERN_B),
        ])
        result = pipeline.apply(text)
    """

    def __init__(self, filters: list[OutputFilter] | None = None) -> None:
        self._filters: list[OutputFilter] = list(filters) if filters else []

    def apply(self, text: str) -> str:
        """Apply all filters in sequence."""
        result = text
        for f in self._filters:
            result = f.apply(result)
        return result

    def with_filter(self, f: OutputFilter) -> FilterPipeline:
        """Return a new pipeline with an additional filter appended.

        The original pipeline is not modified (immutable pattern).
        """
        return FilterPipeline([*self._filters, f])

    def with_filters(self, filters: list[OutputFilter]) -> FilterPipeline:
        """Return a new pipeline with additional filters appended."""
        return FilterPipeline([*self._filters, *filters])

    def __len__(self) -> int:
        return len(self._filters)

    def __repr__(self) -> str:
        return f"FilterPipeline({self._filters!r})"
