r"""Plain text file configuration management.

Provides TextConfigFile for managing text files with required content and user
extensions. Validates via substring matching, preserves user additions.

Example:
    >>> from pathlib import Path
    >>> from pyrig.dev.configs.base.text import TextConfigFile
    >>>
    >>> class LicenseFile(TextConfigFile):
    ...     @classmethod
    ...     def get_parent_path(cls) -> Path:
    ...         return Path()
    ...
    ...     @classmethod
    ...     def get_content_str(cls) -> str:
    ...         return "MIT License\n\nCopyright (c) 2024"
    ...
    ...     @classmethod
    ...     def get_filename(cls) -> str:
    ...         return "LICENSE"
    ...
    ...     @classmethod
    ...     def get_file_extension(cls) -> str:
    ...         return ""
"""

from abc import abstractmethod
from typing import Any

from pyrig.dev.configs.base.base import ConfigFile


class TextConfigFile(ConfigFile):
    r"""Abstract base class for text files with required content validation.

    Validates via substring matching, preserves user additions when updating.

    Attributes:
        CONTENT_KEY: Dictionary key for file content ("content").

    Subclasses must implement:
        - `get_parent_path`: Directory containing the text file
        - `get_content_str`: Required content that must be present
        - `get_file_extension`: File extension (can be empty string)

    See Also:
        pyrig.dev.configs.base.python.PythonConfigFile: For .py files
        pyrig.dev.configs.base.markdown.MarkdownConfigFile: For .md files
    """

    CONTENT_KEY = "content"

    @classmethod
    @abstractmethod
    def get_content_str(cls) -> str:
        r"""Return required content that must be present in file.

        Returns:
            Content string validated via substring matching.
        """

    @classmethod
    def _load(cls) -> dict[str, str]:
        r"""Load file content as UTF-8 text wrapped in dict.

        Returns:
            Dict with CONTENT_KEY containing full file content.
        """
        return {cls.CONTENT_KEY: cls.get_path().read_text(encoding="utf-8")}

    @classmethod
    def _dump(cls, config: dict[str, Any] | list[Any]) -> None:
        r"""Write content to file, preserving user additions by appending.

        Args:
            config: Dict with content under CONTENT_KEY.

        Raises:
            TypeError: If config is not a dict.
        """
        if not isinstance(config, dict):
            msg = f"Cannot dump {config} to text file."
            raise TypeError(msg)
        if not cls.override_content() and cls.get_file_content().strip():
            config[cls.CONTENT_KEY] = (
                config[cls.CONTENT_KEY] + "\n" + cls.get_file_content()
            )
        cls.get_path().write_text(config[cls.CONTENT_KEY], encoding="utf-8")

    @classmethod
    def override_content(cls) -> bool:
        """Override file content even if it exists.

        If True the content of the TextConfigFile subclass will replace the
        existing content. If False the content will be appended to the existing
        content.

        Returns:
            True if content should be overridden, False if not.
        """
        return False

    @classmethod
    def get_configs(cls) -> dict[str, Any]:
        r"""Return dict with required content under CONTENT_KEY.

        Returns:
            Dict with required content from get_content_str().
        """
        return {cls.CONTENT_KEY: cls.get_content_str()}

    @classmethod
    def is_correct(cls) -> bool:
        r"""Check if file contains required content via substring matching.

        Returns:
            True if empty, exact match, or required content present anywhere.
        """
        return (
            super().is_correct()
            or cls.get_content_str().strip() in cls.get_file_content()
        )

    @classmethod
    def get_file_content(cls) -> str:
        r"""Get the current file content.

        Convenience method to get the file content as a string without the
        dict wrapper. Equivalent to cls.load()[cls.CONTENT_KEY].

        Returns:
            The full content of the file as a string.

        Example:
            Get file content::

                # myfile.txt contains:
                # Line 1
                # Line 2

                content = MyTextConfigFile.get_file_content()
                # Returns: "Line 1\nLine 2\n"
        """
        return cls.load()[cls.CONTENT_KEY]
