"""Main Textual application for Lettera."""

import re
from importlib.metadata import version as _pkg_version
from typing import List, Optional

from textual import work
from textual.app import App, ComposeResult
from textual.binding import Binding
from textual.containers import Vertical
from textual.reactive import reactive
from textual.screen import Screen, ModalScreen
from textual.widgets import Footer, Input, Label, Static

from .crypto import decrypt_content, is_encrypted
from .service import Service
from .types import EntryMeta
from .widgets.archive import ArchiveList
from .widgets.editor import TypewriterEditor
from .widgets.fade import FadeOverlay
from .widgets.header import HeaderBar

try:
    __version__ = _pkg_version("lettera")
except Exception:
    __version__ = "dev"

_SPLASH_ART = (
    "▗▖   ▗▄▄▄▖▗▄▄▄▖▗▄▄▄▖▗▄▄▄▖▗▄▄▖  ▗▄▖ \n"
    "▐▌   ▐▌     █    █  ▐▌   ▐▌ ▐▌▐▌ ▐▌\n"
    "▐▌   ▐▛▀▀▘  █    █  ▐▛▀▀▘▐▛▀▚▖▐▛▀▜▌\n"
    "▐▙▄▄▖▐▙▄▄▖  █    █  ▐▙▄▄▖▐▌ ▐▌▐▌ ▐▌"
)


class SplashScreen(Screen):
    """2-second launch splash showing ASCII art and version."""

    DEFAULT_CSS = """
    SplashScreen {
        align: center middle;
        background: #1a1a1a;
    }

    SplashScreen #splash-art {
        color: #4a9eff;
        text-style: bold;
        content-align: center middle;
        width: 100%;
        text-align: center;
        margin-top: 4;
    }

    SplashScreen #splash-version {
        color: #666666;
        content-align: center middle;
        width: 100%;
        text-align: center;
        margin-top: 1;
    }
    """

    def compose(self) -> ComposeResult:
        with Vertical():
            yield Static(_SPLASH_ART, id="splash-art")
            yield Static(f"v{__version__}", id="splash-version")

    def on_mount(self) -> None:
        def _go() -> None:
            self.dismiss()
        self.set_timer(2.0, _go)


class PassphraseModal(ModalScreen):
    """Modal dialog for passphrase entry."""

    BINDINGS = [
        Binding("escape", "cancel", "Cancel", priority=True),
    ]

    DEFAULT_CSS = """
    PassphraseModal {
        align: center middle;
    }

    PassphraseModal > Vertical {
        background: #242424;
        border: solid #4a9eff;
        padding: 2 4;
        width: 52;
        height: auto;
    }

    PassphraseModal Label {
        color: #e0e0e0;
        margin-bottom: 1;
    }

    PassphraseModal Input {
        background: #1a1a1a;
        color: #e0e0e0;
        border: solid #666666;
        margin-top: 1;
    }
    """

    def __init__(self, prompt: str = "Enter passphrase.", *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._prompt = prompt

    def compose(self) -> ComposeResult:
        with Vertical():
            yield Label(self._prompt)
            yield Input(password=True, id="passphrase-input")

    def on_input_submitted(self, event: Input.Submitted) -> None:
        self.dismiss(event.value or None)

    def action_cancel(self) -> None:
        self.dismiss(None)


class FilenameModal(ModalScreen):
    """Modal dialog for naming a new entry."""

    BINDINGS = [
        Binding("escape", "use_default", "Use default", priority=True),
    ]

    DEFAULT_CSS = """
    FilenameModal {
        align: center middle;
    }

    FilenameModal > Vertical {
        background: #242424;
        border: solid #4a9eff;
        padding: 2 4;
        width: 60;
        height: auto;
    }

    FilenameModal #filename-prompt {
        color: #666666;
        margin-bottom: 1;
    }

    FilenameModal Input {
        background: #1a1a1a;
        color: #e0e0e0;
        border: solid #666666;
        margin-top: 1;
    }

    FilenameModal #filename-preview {
        color: #888888;
        margin-top: 1;
    }

    FilenameModal #filename-error {
        color: #e06c75;
        margin-top: 1;
    }
    """

    def __init__(self, default_name: str, service: "Service", *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._default_name = default_name
        self._service = service
        self._conflict = False

    def compose(self) -> ComposeResult:
        with Vertical():
            yield Label("Name this page. Enter confirms, Esc keeps the default.", id="filename-prompt")
            yield Input(value=self._default_name, id="filename-input")
            yield Label(f"→ {self._default_name}.md", id="filename-preview")
            yield Label("", id="filename-error")

    def _sanitize(self, value: str) -> str:
        name = value.strip().replace(" ", "-")
        name = re.sub(r"[^a-zA-Z0-9\-_]", "", name).strip("-_")
        return name if name else self._default_name

    def on_input_changed(self, event: Input.Changed) -> None:
        sanitized = self._sanitize(event.value)
        self.query_one("#filename-preview", Label).update(f"→ {sanitized}.md")
        if self._service.entry_exists(sanitized):
            self._conflict = True
            self.query_one("#filename-error", Label).update(
                f"A page named '{sanitized}.md' already exists."
            )
        else:
            self._conflict = False
            self.query_one("#filename-error", Label).update("")

    def on_input_submitted(self, event: Input.Submitted) -> None:
        sanitized = self._sanitize(event.value)
        # Re-check at submit time to guard against races
        if self._service.entry_exists(sanitized):
            self._conflict = True
            self.query_one("#filename-error", Label).update(
                f"A page named '{sanitized}.md' already exists."
            )
            return
        self.dismiss(sanitized)

    def action_use_default(self) -> None:
        self.dismiss(self._default_name)


class ConfirmNewPageModal(ModalScreen):
    """Modal dialog for confirming a new page when content is unsaved."""

    BINDINGS = [
        Binding("enter", "start_fresh", "Start fresh", priority=True),
        Binding("escape", "keep_writing", "Keep writing", priority=True),
        Binding("y", "start_fresh", "Start fresh", priority=True),
        Binding("n", "keep_writing", "Keep writing", priority=True),
    ]

    DEFAULT_CSS = """
    ConfirmNewPageModal {
        align: center middle;
    }

    ConfirmNewPageModal > Vertical {
        background: #242424;
        border: solid #4a9eff;
        padding: 2 4;
        width: 66;
        height: auto;
    }

    ConfirmNewPageModal Label {
        color: #e0e0e0;
        margin-bottom: 1;
    }

    ConfirmNewPageModal .hint {
        color: #666666;
        margin-top: 1;
    }
    """

    def compose(self) -> ComposeResult:
        with Vertical():
            yield Label("Start a fresh page? Unsaved writing will be cleared.")
            yield Label("Enter/Y: Start fresh", classes="hint")
            yield Label("Esc/N: Keep writing", classes="hint")

    def action_start_fresh(self) -> None:
        self.dismiss(True)

    def action_keep_writing(self) -> None:
        self.dismiss(False)


class ConfirmQuitModal(ModalScreen):
    """Modal dialog for confirming quit when content is unsaved."""

    BINDINGS = [
        Binding("enter", "quit_now", "Quit", priority=True),
        Binding("escape", "keep_writing", "Keep writing", priority=True),
        Binding("y", "quit_now", "Quit", priority=True),
        Binding("n", "keep_writing", "Keep writing", priority=True),
    ]

    DEFAULT_CSS = """
    ConfirmQuitModal {
        align: center middle;
    }

    ConfirmQuitModal > Vertical {
        background: #242424;
        border: solid #4a9eff;
        padding: 2 4;
        width: 66;
        height: auto;
    }

    ConfirmQuitModal Label {
        color: #e0e0e0;
        margin-bottom: 1;
    }

    ConfirmQuitModal .hint {
        color: #666666;
        margin-top: 1;
    }
    """

    def compose(self) -> ComposeResult:
        with Vertical():
            yield Label("Quit now? Unsaved writing will be lost.")
            yield Label("Enter/Y: Quit", classes="hint")
            yield Label("Esc/N: Keep writing", classes="hint")

    def action_quit_now(self) -> None:
        self.dismiss(True)

    def action_keep_writing(self) -> None:
        self.dismiss(False)


class ShortcutsHelpModal(ModalScreen):
    """One-time shortcuts onboarding modal."""

    DEFAULT_CSS = """
    ShortcutsHelpModal {
        align: center middle;
    }

    ShortcutsHelpModal > Vertical {
        background: #242424;
        border: solid #4a9eff;
        padding: 2 4;
        width: 64;
        height: auto;
    }

    ShortcutsHelpModal Label {
        color: #e0e0e0;
        margin-bottom: 1;
    }

    ShortcutsHelpModal .muted {
        color: #888888;
    }
    """

    def compose(self) -> ComposeResult:
        with Vertical():
            yield Label("Writing rituals")
            yield Label("Forward-only: use Ctrl+X to correct.", classes="muted")
            yield Label("Ctrl+S  save")
            yield Label("Ctrl+X  strike through")
            yield Label("Ctrl+L  lock page")
            yield Label("Ctrl+N  new page")
            yield Label("Ctrl+O  open archive")
            yield Label("Ctrl+Q  quit")
            yield Label("Ctrl+K  show shortcuts")
            yield Label("Press any key to continue.", classes="muted")

    def on_key(self, event) -> None:
        event.prevent_default()
        event.stop()
        self.dismiss()


class EditorScreen(Screen):
    """Main editor screen."""

    BINDINGS = [
        Binding("ctrl+x", "strikethrough", "Strike", priority=True),
        Binding("ctrl+s", "save", "Save", priority=True),
        Binding("ctrl+l", "protect_entry", "Lock", priority=True),
        Binding("ctrl+n", "new_page", "New", priority=True),
        Binding("ctrl+o", "open_archive", "Archive", priority=True),
        Binding("ctrl+k", "show_shortcuts", "Shortcuts", priority=True),
        Binding("ctrl+q", "quit_app", "Quit", priority=True),
    ]

    current_file = reactive(None)

    def __init__(self, service: Service, *args, **kwargs):
        """Initialize editor screen."""
        super().__init__(*args, **kwargs)
        self.service = service
        self._passphrase: Optional[str] = None
        self._current_entry_encrypted: bool = False
        self._has_shown_blocked_key_hint: bool = False

    def compose(self) -> ComposeResult:
        """Compose the editor screen."""
        yield HeaderBar(show_state=True, id="header")
        yield TypewriterEditor(id="editor")
        yield Footer()
        yield FadeOverlay(id="fade")

    async def on_mount(self) -> None:
        """Set up the editor when mounted."""
        # Create a new entry file path for the initial session
        self.current_file = self.service.create_entry_path()
        self._update_header()

        if not self.service.config.help_seen:
            self.service.config.help_seen = True
            self.service.config.save()
            self.app.push_screen(ShortcutsHelpModal())

    async def action_strikethrough(self) -> None:
        """Apply strikethrough to character at cursor (Ctrl+X)."""
        editor = self.query_one("#editor", TypewriterEditor)
        editor._apply_strikethrough()

    def action_show_shortcuts(self) -> None:
        """Show shortcuts help modal (Ctrl+K)."""
        self.app.push_screen(ShortcutsHelpModal())

    def _has_unsaved_writing(self, editor: TypewriterEditor) -> bool:
        """Return True when there is text that has not been saved."""
        if editor.has_unsaved_changes:
            return True

        if not editor.text.strip():
            return False

        # A non-empty page backed by a path that doesn't exist is still unsaved.
        return bool(self.current_file and not self.current_file.exists())

    def _update_header(self) -> None:
        """Sync header bar state with current editor and entry state."""
        header = self.query_one("#header", HeaderBar)
        editor = self.query_one("#editor", TypewriterEditor)

        if self.current_file and self.current_file.exists():
            header.entry_name = self.current_file.stem
        else:
            header.entry_name = "untitled"

        header.is_locked = self._current_entry_encrypted
        header.is_saved = not self._has_unsaved_writing(editor)

    def on_text_area_changed(self, event: object) -> None:
        """Update header save state whenever editor content changes."""
        self._update_header()

    @work
    async def action_save(self) -> None:
        """Save current entry (Ctrl+S)."""
        editor = self.query_one("#editor", TypewriterEditor)
        content = editor.text

        if not content.strip():
            # Don't save empty files
            self.notify("This page is empty.", severity="warning")
            return

        if not editor.has_unsaved_changes:
            # Nothing to save, but still show closure ritual
            await self._trigger_closure_ritual()
            return

        # First save: prompt for a custom filename
        if not self.current_file.exists():
            default_stem = self.current_file.stem
            chosen = await self.app.push_screen_wait(
                FilenameModal(default_name=default_stem, service=self.service)
            )
            self.current_file = self.current_file.parent / f"{chosen}.md"

        # Save the entry (re-encrypt if this entry is protected)
        await self.service.save_entry(
            self.current_file,
            content,
            encrypt=self._current_entry_encrypted,
            passphrase=self._passphrase if self._current_entry_encrypted else None,
        )
        editor.has_unsaved_changes = False
        self._update_header()

        # Trigger closure ritual
        await self._trigger_closure_ritual(message="Saved")

    @work
    async def action_new_page(self) -> None:
        """Create a new page (Ctrl+N)."""
        editor = self.query_one("#editor", TypewriterEditor)

        if self._has_unsaved_writing(editor):
            start_fresh = await self.app.push_screen_wait(ConfirmNewPageModal())
            if not start_fresh:
                return

        editor.clear_content()
        self.current_file = self.service.create_entry_path()
        self._current_entry_encrypted = False
        self._update_header()

    async def on_typewriter_editor_deletion_blocked(
        self, event: TypewriterEditor.DeletionBlocked
    ) -> None:
        """Show a one-time hint when deletion keys are blocked."""
        if self._has_shown_blocked_key_hint:
            return

        self._has_shown_blocked_key_hint = True
        key_label = "Backspace" if event.key == "backspace" else "Delete"
        self.notify(
            f"{key_label} is disabled. Use Ctrl+X to strike through.",
            severity="information",
        )

    @work
    async def action_protect_entry(self) -> None:
        """Encrypt and lock the current entry (Ctrl+L)."""
        editor = self.query_one("#editor", TypewriterEditor)
        content = editor.text

        if not content.strip():
            return

        if self._passphrase is None:
            passphrase = await self.app.push_screen_wait(PassphraseModal())
            if not passphrase:
                return
            self._passphrase = passphrase

        await self.service.save_entry(
            self.current_file,
            content,
            encrypt=True,
            passphrase=self._passphrase,
        )
        self._current_entry_encrypted = True
        editor.has_unsaved_changes = False
        self._update_header()
        self.notify("Page locked.", severity="information")

    @work
    async def action_open_archive(self) -> None:
        """Open archive screen (Ctrl+O)."""
        entries = await self.service.list_entries()
        archive_screen = ArchiveScreen(self.service, entries)
        entry = await self.app.push_screen_wait(archive_screen)
        if entry is not None:
            await self.load_entry(entry)

    async def _confirm_and_quit(self) -> None:
        """Confirm unsaved changes (if any), then run quit ritual."""
        editor = self.query_one("#editor", TypewriterEditor)

        if self._has_unsaved_writing(editor):
            quit_now = await self.app.push_screen_wait(ConfirmQuitModal())
            if not quit_now:
                return

        # Show closure ritual before quitting
        fade = self.query_one("#fade", FadeOverlay)
        await fade.animate(action_type="quit", message="Bye")
        self.app.exit()

    @work
    async def action_quit_app(self) -> None:
        """Quit the application (Ctrl+Q)."""
        await self._confirm_and_quit()

    async def _trigger_closure_ritual(self, message: Optional[str] = None) -> None:
        """Trigger the fade-to-black closure ritual."""
        fade = self.query_one("#fade", FadeOverlay)
        await fade.animate(action_type="save", message=message)

    async def load_entry(self, entry: EntryMeta) -> None:
        """Load an entry from the archive, decrypting if necessary."""
        try:
            raw = await self.service.load_entry(entry.path)

            if is_encrypted(raw):
                max_attempts = 3
                passphrase = self._passphrase
                content = None

                for attempt in range(max_attempts):
                    if passphrase is None:
                        if attempt == 0:
                            prompt = "Enter passphrase."
                        else:
                            retries_left = max_attempts - attempt
                            plural = "y" if retries_left == 1 else "ies"
                            prompt = (
                                f"Wrong passphrase. {retries_left} retr{plural} left."
                            )
                        passphrase = await self.app.push_screen_wait(
                            PassphraseModal(prompt=prompt)
                        )
                        if not passphrase:
                            return

                    try:
                        content = decrypt_content(passphrase, raw)
                        self._passphrase = passphrase
                        break
                    except ValueError:
                        passphrase = None
                        self._passphrase = None
                else:
                    self.notify(
                        "Wrong passphrase. Could not open entry.", severity="error"
                    )
                    return

                self._current_entry_encrypted = True
            else:
                content = raw
                self._current_entry_encrypted = False

            editor = self.query_one("#editor", TypewriterEditor)
            editor.load_content(content)
            self.current_file = entry.path
            self._update_header()
        except Exception as e:
            self.app.log(f"Error loading entry: {e}")


class ArchiveScreen(Screen):
    """Archive browsing screen."""

    BINDINGS = [
        Binding("escape", "back_to_editor", "Back", priority=True),
    ]

    def __init__(self, service: Service, entries: List[EntryMeta], *args, **kwargs):
        """Initialize archive screen."""
        super().__init__(*args, **kwargs)
        self.service = service
        self.entries = entries

    def compose(self) -> ComposeResult:
        """Compose the archive screen."""
        yield HeaderBar(id="header")
        with Vertical():
            yield ArchiveList(self.entries, id="archive")
        yield Footer()

    async def on_archive_list_entry_selected(
        self, event: ArchiveList.EntrySelected
    ) -> None:
        """Handle entry selection from archive."""
        self.dismiss(event.entry)

    async def action_back_to_editor(self) -> None:
        """Return to editor (Esc)."""
        self.dismiss(None)


class LetteraApp(App):
    """Lettera typewriter journaling application."""

    CSS = """
    Screen {
        background: #1a1a1a;
    }

    #header {
        dock: top;
        height: 3;
        background: #242424;
        color: #e0e0e0;
        content-align: center middle;
        text-style: bold;
    }

    #editor {
        background: #1a1a1a;
        color: #e0e0e0;
        border: none;
        padding: 2 4;
    }

    #editor .text-area--cursor-line {
        background: #20252d;
    }

    #editor.typing-pulse .text-area--cursor {
        background: #4a9eff;
        color: #1a1a1a;
        text-style: bold;
    }

    Footer {
        background: #242424;
    }

    Footer .footer--key {
        color: #4a9eff;
    }

    Footer .footer--description {
        color: #888888;
    }

    #archive {
        background: #1a1a1a;
        border: solid #4a9eff;
        height: 100%;
    }

    #archive > ListItem {
        padding: 1 2;
    }

    #archive > ListItem:hover {
        background: #242424;
    }

    #archive > ListItem.empty {
        color: #666666;
        text-style: italic;
    }

    #archive > ListItem.date-header {
        color: #666666;
        text-style: bold;
        background: #1a1a1a;
        border-top: solid #242424;
    }

    FadeOverlay {
        background: black;
    }
    """

    def __init__(self):
        """Initialize the Lettera app."""
        super().__init__()
        self.service = Service()

    @work
    async def action_quit(self) -> None:
        """Handle app-level quit binding through editor confirmation flow."""
        current_screen = self.screen
        if isinstance(current_screen, EditorScreen):
            await current_screen._confirm_and_quit()
            return

        self.exit()

    def on_mount(self) -> None:
        """Show splash screen, then launch the editor."""
        self.push_screen(SplashScreen(), callback=self._launch_editor)

    def _launch_editor(self, _) -> None:
        self.push_screen(EditorScreen(self.service))
