"""
Chitra Editor Utility.
~~~~~~~~~~~~~~~~~~~~~

This module provides an asynchronous text editor built using prompt_toolkit
and aiofiles. The editor supports features such as syntax highlighting for
Python code, dynamic theme toggling, file open/save dialogs, and key bindings.
It is designed with full type safety and can be extended in the future.

Classes
-------
ChitraEditor
    Main editor class that constructs the UI, handles key bindings, and manages
    file operations.

Type Aliases
------------
CursorPosition : NewType[int]
    Alias representing the cursor position.
FilePath : NewType[str]
    Alias representing the file path.
ThemeDict : Dict[str, str]
    Alias for theme dictionary.
EditorTheme : TypedDict
    Typed dictionary specifying theme settings for text area and status bar.
"""

import asyncio
import os
import typing

import aiofiles
import prompt_toolkit.application
import prompt_toolkit.auto_suggest
import prompt_toolkit.key_binding
import prompt_toolkit.keys
import prompt_toolkit.layout
import prompt_toolkit.layout.containers
import prompt_toolkit.lexers
import prompt_toolkit.shortcuts.dialogs
import prompt_toolkit.styles
import prompt_toolkit.widgets
import pygments.lexers.python

# Type aliases for better readability
CursorPosition = typing.NewType(
    "CursorPosition",
    int,
)
FilePath = typing.NewType(
    "FilePath",
    str,
)
ThemeDict = typing.Dict[str, str]


class EditorTheme(typing.TypedDict):
    """
    Typed dictionary representing editor theme configuration.

    Attributes
    ----------
    text_area : str
        The style string for the text area.
    status_bar : str
        The style string for the status bar.
    """

    text_area: str
    status_bar: str


class ChitraEditor:
    """
    Asynchronous text editor with theme support, file operations, and key bindings.

    This class builds a full-screen editor using prompt_toolkit components.
    It supports syntax highlighting, dynamic theming, and asynchronous file
    open/save operations.

    Methods
    -------
    run(directory: Optional[str] = None) -> None
        Start the editor application.
    """

    def __init__(
        self,
    ) -> None:
        """Initialize the ChitraEditor with default themes, components, and key bindings."""
        self.search_toolbar = prompt_toolkit.widgets.SearchToolbar()
        self.current_dir: typing.Optional[str] = None
        self.current_file: typing.Optional[FilePath] = None
        self.cursor_position: CursorPosition = CursorPosition(0)

        # Initialize themes
        self.light_theme: EditorTheme = {
            "text_area": "bg:#ffffff fg:#000000",
            "status_bar": "bg:#eeeeee fg:#000000",
        }
        self.dark_theme: EditorTheme = {
            "text_area": "bg:#1e1e1e fg:#d4d4d4",
            "status_bar": "bg:#333333 fg:#ffffff",
        }
        self.current_theme: prompt_toolkit.styles.Style = self._create_style(
            self.light_theme
        )

        # Initialize components
        self.text_area: prompt_toolkit.widgets.TextArea = (
            self._create_text_area()
        )
        self.status_bar: prompt_toolkit.widgets.Label = (
            self._create_status_bar()
        )
        self.bindings: prompt_toolkit.key_binding.KeyBindings = (
            self._create_key_bindings()
        )
        self.layout: prompt_toolkit.layout.Layout = self._create_layout()

    def _create_style(
        self,
        theme: EditorTheme,
    ) -> prompt_toolkit.styles.Style:
        """
        Create a prompt_toolkit style from the provided theme.

        Parameters
        ----------
        theme : EditorTheme
            The theme dictionary with style definitions.

        Returns
        -------
        prompt_toolkit.styles.Style
            The constructed style object.
        """
        return prompt_toolkit.styles.Style.from_dict(
            {
                "text-area": theme["text_area"],
                "status-bar": theme["status_bar"],
            }
        )

    def _create_text_area(
        self,
    ) -> prompt_toolkit.widgets.TextArea:
        """
        Create the text area component with Python syntax highlighting.

        Returns
        -------
        prompt_toolkit.widgets.TextArea
            The constructed text area widget.
        """
        return prompt_toolkit.widgets.TextArea(
            lexer=prompt_toolkit.lexers.PygmentsLexer(
                pygments.lexers.python.PythonLexer
            ),
            scrollbar=True,
            line_numbers=True,
            multiline=True,
            wrap_lines=False,
            auto_suggest=prompt_toolkit.auto_suggest.AutoSuggestFromHistory(),
            search_field=self.search_toolbar,
        )

    def _create_status_bar(
        self,
    ) -> prompt_toolkit.widgets.Label:
        """
        Create the status bar component displaying cursor position and word count.

        Returns
        -------
        prompt_toolkit.widgets.Label
            The constructed status bar widget.
        """
        return prompt_toolkit.widgets.Label(text=self._get_status_text())

    def _get_status_text(
        self,
    ) -> str:
        """
        Generate the status text based on current cursor position and word count.

        Returns
        -------
        str
            The status string.
        """
        word_count = (
            len(self.text_area.text.split())
            if hasattr(
                self,
                "text_area",
            )
            else 0
        )
        return (
            f"Cursor: {self.cursor_position} | Words: {word_count} | "
            "Save As: Q+S+A+V+E | Open: Ctrl+O | Quit: Q+E+X+I+T"
        )

    def _create_layout(
        self,
    ) -> prompt_toolkit.layout.Layout:
        """
        Create the application layout with a text area, search toolbar, and status bar.

        Returns
        -------
        prompt_toolkit.layout.Layout
            The constructed layout.
        """
        return prompt_toolkit.layout.Layout(
            prompt_toolkit.layout.containers.HSplit(
                [
                    prompt_toolkit.widgets.Frame(
                        title="ChitraGupt Pad",
                        body=self.text_area,
                    ),
                    self.search_toolbar,
                    self.status_bar,
                ]
            )
        )

    def _create_key_bindings(
        self,
    ) -> prompt_toolkit.key_binding.KeyBindings:
        """
        Create key bindings for common editor actions.

        Returns
        -------
        prompt_toolkit.key_binding.KeyBindings
            The key bindings object.
        """
        bindings = prompt_toolkit.key_binding.KeyBindings()

        @bindings.add("tab")
        def _(
            event: prompt_toolkit.key_binding.KeyPressEvent,
        ) -> None:
            """Insert four spaces when Tab is pressed."""
            event.app.current_buffer.insert_text("    ")

        @bindings.add(
            "q",
            "t",
            "h",
            "e",
            "m",
            "e",
        )
        def _(
            event: prompt_toolkit.key_binding.KeyPressEvent,
        ) -> None:
            """Toggle between light and dark themes."""
            self._toggle_theme()
            event.app.invalidate()

        @bindings.add(
            "q",
            "s",
            "a",
            "v",
            "e",
        )
        def _(
            event: prompt_toolkit.key_binding.KeyPressEvent,
        ) -> None:
            """Save the current file asynchronously."""
            asyncio.create_task(self._save_file())  # noqa: RUF006

        @bindings.add(prompt_toolkit.keys.Keys.ControlO)
        def _(
            event: prompt_toolkit.key_binding.KeyPressEvent,
        ) -> None:
            """Open a file asynchronously."""
            asyncio.create_task(self._open_file())  # noqa: RUF006

        @bindings.add(
            "q",
            "e",
            "x",
            "i",
            "t",
        )
        def _(
            event: prompt_toolkit.key_binding.KeyPressEvent,
        ) -> None:
            """Exit the editor."""
            event.app.exit()

        return bindings

    def _toggle_theme(
        self,
    ) -> None:
        """Toggle between the light and dark themes."""
        if self.current_theme == self._create_style(self.light_theme):
            self.current_theme = self._create_style(self.dark_theme)
        else:
            self.current_theme = self._create_style(self.light_theme)

    async def _save_file(
        self,
    ) -> None:
        """Asynchronously prompt the user for a file name and save the current text content."""
        file_name: typing.Optional[
            str
        ] = await prompt_toolkit.shortcuts.dialogs.input_dialog(
            title="Save As",
            text="Enter file name:",
        ).run_async()

        if not file_name:
            return

        try:
            full_path = os.path.join(
                self.current_dir or os.getcwd(),
                file_name,
            )
            async with aiofiles.open(
                full_path,
                mode="w",
                encoding="utf-8",
            ) as f:
                await f.write(self.text_area.text)
            self.current_file = FilePath(full_path)
            self.status_bar.text = f"Saved to {full_path}"
        except Exception as e:
            self.status_bar.text = f"Error saving file: {e}"

    async def _open_file(
        self,
    ) -> None:
        """Asynchronously prompt the user for a file name and load its content into the text area."""
        file_name: typing.Optional[
            str
        ] = await prompt_toolkit.shortcuts.dialogs.input_dialog(
            title="Open File",
            text="Enter file name:",
        ).run_async()

        if not file_name:
            return

        try:
            full_path = os.path.join(
                self.current_dir or os.getcwd(),
                file_name,
            )
            async with aiofiles.open(
                full_path,
                mode="r",
                encoding="utf-8",
            ) as f:
                self.text_area.text = await f.read()
            self.current_file = FilePath(full_path)
            self.status_bar.text = f"Opened {full_path}"
        except Exception as e:
            self.status_bar.text = f"Error opening file: {e}"

    def run(
        self,
        directory: typing.Optional[str] = None,
    ) -> None:
        """
        Run the editor application in full-screen mode.

        Parameters
        ----------
        directory : str, optional
            The directory to set as current. If not provided, the current working
            directory is used.
        """
        self.current_dir = directory
        app: prompt_toolkit.application.Application = (
            prompt_toolkit.application.Application(  # type: ignore
                layout=self.layout,
                key_bindings=self.bindings,
                full_screen=True,
                style=self.current_theme,
                mouse_support=True,
                refresh_interval=0.05,
            )
        )
        app.run()
