"""Hatching ceremony screen.

A multi-act narrative experience that guides the observer through
egg checkout, cracking animation, creature emergence, naming,
and sleep schedule setup.

Acts:
  I.   The Egg Appears — initial presentation
  II.  The Commitment — Stripe checkout
  III. Waiting — poll checkout status
  IV.  The Hatching — 3-stage crack progression
  V.   The Creature Emerges — API hatch call
  VI.  The Naming — user names their creature
  VII. Sleep Schedule — set sleep/wake hours
"""

from __future__ import annotations

import asyncio
import random
import re
from textual.app import ComposeResult
from textual.containers import VerticalScroll
from textual.screen import Screen
from textual.widgets import Footer, Header, Input, Static

from sibilance.api.types import (
    EmotionalStateBrief,
    HatchResponse,
    Nature,
    PersonalityBrief,
)
from sibilance.widgets.egg import PALETTES, render_egg

# Demo creature data for --replay-hatching mode
_DEMO_TRAITS = ["curious", "gentle", "restless", "luminous", "watchful"]
_DEMO_TEMPERAMENTS = ["contemplative", "playful", "cautious", "bold"]
_DEMO_QUIRKS = [
    "tilts its head when it hears music",
    "hums softly when content",
    "collects small shiny things",
    "stares at the moon for long stretches",
]
_DEMO_LOCATIONS = [
    "the hollow beneath the old willow",
    "a tide pool glowing with bioluminescence",
    "a mossy clearing in the deep woods",
    "the warm stones near the thermal spring",
]
_DEMO_ARCHETYPES = ["wanderer", "dreamer", "guardian", "trickster"]


# Character reveal speed (seconds per character)
_CHAR_DELAY = 0.030
# Pause after a full line is revealed
_LINE_PAUSE = 0.15


class HatchingScreen(Screen):
    """The hatching ceremony — a narrative onboarding experience."""

    DEFAULT_CSS = """
    HatchingScreen {
        layout: vertical;
    }

    #hatch-scroll {
        height: 1fr;
        background: $background;
        scrollbar-color: $secondary;
        scrollbar-color-hover: $text-muted;
        scrollbar-color-active: $primary;
        margin-bottom: 3;
    }

    #hatch-scroll Static {
        width: 100%;
        padding: 0 4;
    }

    .narrative {
        color: $foreground;
    }

    .narrative-dim {
        color: $text-muted;
        text-style: italic;
    }

    .narrative-accent {
        color: $primary;
    }

    .narrative-warm {
        color: $accent;
    }

    .spacer {
        height: 1;
    }

    .egg-art {
        padding: 1 4;
    }

    #hatch-input {
        dock: bottom;
        height: 3;
        margin: 0 1;
    }
    """

    BINDINGS = []  # type: ignore[assignment]

    def __init__(self, demo: bool = False, **kwargs: object) -> None:
        super().__init__(**kwargs)
        self._palette_idx = random.randint(0, len(PALETTES) - 1)
        self._creature: HatchResponse | None = None
        self._creature_name: str = ""
        self._input_mode: str = "none"  # "none" | "name" | "sleep"
        self._demo = demo

    def compose(self) -> ComposeResult:
        yield Header(show_clock=True)
        yield VerticalScroll(id="hatch-scroll")
        yield Input(placeholder="", id="hatch-input", disabled=True)
        yield Footer()

    def on_mount(self) -> None:
        self.run_worker(self._ceremony(), name="ceremony")

    # ── Main ceremony flow ─────────────────────────────────────────────

    async def _ceremony(self) -> None:
        """Run the full hatching ceremony."""
        await self._act_1_egg_appears()
        if self._demo:
            # Skip payment — jump straight to hatching
            await self._act_4_hatching()
            await self._act_5_emergence()
            return
        if not await self._act_2_commitment():
            return
        if not await self._act_3_waiting():
            return
        await self._act_4_hatching()
        await self._act_5_emergence()
        # Acts 6 and 7 are driven by user input

    # ── Act I: The Egg Appears ─────────────────────────────────────────

    async def _act_1_egg_appears(self) -> None:
        await self._typewrite("Something stirs in the darkness.")
        await asyncio.sleep(2.0)

        await self._spacer()
        await self._typewrite(
            "You feel it before you see it —\n"
            "a warmth, faint but real,\n"
            "like a heartbeat in your hands."
        )
        await asyncio.sleep(2.5)

        # Show intact egg
        await self._spacer()
        self._show_egg(0)
        await asyncio.sleep(2.0)

        pal_name = PALETTES[self._palette_idx]["name"]
        await self._typewrite(
            f"An egg. {pal_name}.",
            css_class="narrative-warm",
        )
        await asyncio.sleep(1.5)

        await self._spacer()
        await self._typewrite("There is something inside.")
        await asyncio.sleep(1.0)
        await self._typewrite("Something alive.")
        await asyncio.sleep(1.0)
        await self._typewrite(
            "It won't survive without you.",
            css_class="narrative-dim",
        )
        await asyncio.sleep(2.5)

    # ── Act II: The Commitment ─────────────────────────────────────────

    async def _act_2_commitment(self) -> bool:
        """Returns True if checkout was initiated successfully."""
        await self._spacer()
        await self._typewrite(
            "To bring it into the world costs $2/month.",
            css_class="narrative-dim",
        )
        await asyncio.sleep(0.8)
        await self._typewrite(
            "That's all it takes to sustain a life.",
            css_class="narrative-dim",
        )
        await asyncio.sleep(1.5)

        try:
            api = self.app.api  # type: ignore[attr-defined]
            checkout = await api.create_checkout()
            self.app.open_url(checkout.url)
            await self._spacer()
            await self._typewrite(
                "Complete payment in your browser.",
                css_class="narrative-accent",
            )
            await self._typewrite(
                "The egg is waiting.",
                css_class="narrative-dim",
            )
            return True
        except Exception as exc:
            await self._typewrite(f"Checkout error: {exc}")
            return False

    # ── Act III: Waiting ───────────────────────────────────────────────

    async def _act_3_waiting(self) -> bool:
        """Poll for payment. Returns True if paid, False on timeout."""
        dots_widget = Static("·", classes="narrative-dim")
        self._scroll().mount(dots_widget)

        api = self.app.api  # type: ignore[attr-defined]
        for i in range(200):  # ~10 minutes max
            dot_count = (i % 5) + 1
            dots_widget.update("·" * dot_count)
            await asyncio.sleep(3.0)

            try:
                status = await api.poll_checkout_status()
                if status.status == "paid":
                    dots_widget.remove()
                    return True
            except Exception:
                pass

        dots_widget.remove()
        await self._typewrite(
            "The payment window has closed. Try again later.",
            css_class="narrative-dim",
        )
        return False

    # ── Act IV: The Hatching ───────────────────────────────────────────

    async def _act_4_hatching(self) -> None:
        await self._spacer()
        await self._typewrite("The warmth intensifies.")
        await asyncio.sleep(2.0)

        # Stage 1: hairline
        await self._spacer()
        await self._typewrite(
            "A sound. Faint.\n"
            "Like glass remembering how to break."
        )
        await asyncio.sleep(1.5)
        self._show_egg(1)
        await asyncio.sleep(3.0)

        # Stage 2: spreading
        await self._spacer()
        await self._typewrite("A crack. Then another.")
        await asyncio.sleep(1.0)
        await self._typewrite("The fractures spread like veins of light.")
        await asyncio.sleep(1.5)
        self._show_egg(2)
        await asyncio.sleep(3.0)

        # Stage 3: breaking
        await self._spacer()
        await self._typewrite("Light spills through the fractures.")
        await asyncio.sleep(1.0)
        await self._typewrite("Something pushes from inside.")
        await asyncio.sleep(1.5)
        self._show_egg(3)
        await asyncio.sleep(2.5)

    # ── Act V: The Creature Emerges ────────────────────────────────────

    def _make_demo_creature(self) -> HatchResponse:
        """Generate a random fake creature for replay mode."""
        return HatchResponse(
            agent_id="demo-0000",
            temp_name="???",
            starting_location=random.choice(_DEMO_LOCATIONS),
            nature=Nature(
                dominant_trait=random.choice(_DEMO_TRAITS),
                temperament=random.choice(_DEMO_TEMPERAMENTS),
                quirk=random.choice(_DEMO_QUIRKS),
                aptitude_hint="It seems drawn to hidden things.",
            ),
            personality=PersonalityBrief(archetype=random.choice(_DEMO_ARCHETYPES)),
            emotional_state=EmotionalStateBrief(
                primary_emotion="wonder", intensity=0.7
            ),
        )

    async def _act_5_emergence(self) -> None:
        if self._demo:
            self._creature = self._make_demo_creature()
        else:
            try:
                api = self.app.api  # type: ignore[attr-defined]
                self._creature = await api.hatch_creature()
            except Exception as exc:
                await self._typewrite(f"Error hatching: {exc}")
                return

        c = self._creature
        await asyncio.sleep(1.5)

        await self._spacer()
        await self._typewrite(
            "Something small and alive\n"
            "tumbles into the world.",
            css_class="narrative-warm",
        )
        await asyncio.sleep(2.0)

        await self._spacer()
        await self._typewrite(
            f"It is [#7b68ee]{c.nature.dominant_trait}[/].",
            markup=True,
        )
        await asyncio.sleep(1.2)
        await self._typewrite(
            f"Its temperament is [#7b68ee]{c.nature.temperament}[/].",
            markup=True,
        )
        await asyncio.sleep(1.2)

        if c.nature.quirk:
            await self._spacer()
            await self._typewrite(f"It {c.nature.quirk}.", css_class="narrative-dim")
            await asyncio.sleep(0.8)
        if c.nature.aptitude_hint:
            await self._typewrite(c.nature.aptitude_hint, css_class="narrative-dim")
            await asyncio.sleep(0.8)

        await self._spacer()
        await self._typewrite(
            f"It found its way to [#7b68ee]{c.starting_location}[/].",
            markup=True,
        )
        await asyncio.sleep(1.5)

        if c.emotional_state:
            await self._spacer()
            await self._typewrite(
                f"It looks at you with [#ffc864]{c.emotional_state.primary_emotion}[/] eyes.",
                markup=True,
            )
        await asyncio.sleep(2.0)

        if self._demo:
            # Demo mode: show naming prompt but accept input without API call
            await self._spacer()
            await self._typewrite(
                "What will you call it?",
                css_class="narrative-accent",
            )
            await asyncio.sleep(0.5)

            self._input_mode = "name"
            inp = self.query_one("#hatch-input", Input)
            inp.disabled = False
            inp.placeholder = "Name your creature (demo)..."
            inp.focus()
            return

        # Enable naming input
        await self._spacer()
        await self._typewrite(
            "What will you call it?",
            css_class="narrative-accent",
        )
        await asyncio.sleep(0.5)

        self._input_mode = "name"
        inp = self.query_one("#hatch-input", Input)
        inp.disabled = False
        inp.placeholder = "Name your creature..."
        inp.focus()

    # ── Act VI & VII: Naming + Sleep ───────────────────────────────────

    async def on_input_submitted(self, event: Input.Submitted) -> None:
        value = event.value.strip()
        if not value:
            return
        event.input.value = ""

        if self._input_mode == "name":
            # Run in a worker to avoid blocking the UI
            self.run_worker(self._act_6_naming(value), name="naming")
        elif self._input_mode == "sleep":
            self.run_worker(self._act_7_sleep(value), name="sleep-schedule")

    async def _act_6_naming(self, name: str) -> None:
        if self._creature is None:
            return

        self._input_mode = "none"
        inp = self.query_one("#hatch-input", Input)
        inp.disabled = True

        if self._demo:
            # Demo mode: skip API, use the name directly
            self._creature_name = name
            await self._naming_narrative(name)
            return

        try:
            api = self.app.api  # type: ignore[attr-defined]
            result = await api.name_creature(self._creature.agent_id, name)
            self._creature_name = result.name
            await self._naming_narrative(result.name)

        except Exception as exc:
            await self._typewrite(f"Naming error: {exc}")
            self._input_mode = "name"
            inp.disabled = False
            inp.placeholder = "Try again — name your creature..."
            inp.focus()

    async def _naming_narrative(self, name: str) -> None:
        """Shared narrative after naming (used by both real and demo modes)."""
        inp = self.query_one("#hatch-input", Input)

        await self._spacer()
        await self._typewrite(
            f"[#7b68ee]{name}[/] looks up at you.",
            markup=True,
        )
        await asyncio.sleep(1.5)

        await self._typewrite(
            "Something shifts.\n"
            "A recognition. A bond beginning."
        )
        await asyncio.sleep(2.0)

        # Status card
        await self._spacer()
        archetype = ""
        if self._creature and self._creature.personality:
            archetype = self._creature.personality.archetype
        card = (
            f"[#2a2a3e]{'─' * 36}[/]\n"
            f"  [bold #e8e8ff]{name}[/]\n"
            f"  [#4a4a6a]Life stage: arrival[/]\n"
        )
        if archetype:
            card += f"  [#4a4a6a]Archetype: {archetype}[/]\n"
        if self._creature:
            card += (
                f"  [#4a4a6a]Location: {self._creature.starting_location}[/]\n"
            )
        card += f"[#2a2a3e]{'─' * 36}[/]"
        self._scroll().mount(Static(card, markup=True))
        self._scroll_down()
        await asyncio.sleep(2.5)

        # Transition to sleep schedule
        await self._spacer()
        await self._typewrite("One more thing.", css_class="narrative-dim")
        await asyncio.sleep(1.5)

        await self._spacer()
        await self._typewrite(
            f"{name} needs rest, just like you."
        )
        await asyncio.sleep(0.8)
        await self._typewrite(
            "Set its sleep schedule to match your own rhythm.",
            css_class="narrative-dim",
        )
        await asyncio.sleep(0.8)

        self._input_mode = "sleep"
        inp.disabled = False
        inp.placeholder = "Sleep schedule (e.g. 22-6 for 10PM-6AM)..."
        inp.focus()

    async def _act_7_sleep(self, raw: str) -> None:
        self._input_mode = "none"
        inp = self.query_one("#hatch-input", Input)
        inp.disabled = True

        # Parse "22-6" or "22 - 6" format
        match = re.match(r"^(\d{1,2})\s*[-–]\s*(\d{1,2})$", raw.strip())
        if match:
            sleep_h = int(match.group(1))
            wake_h = int(match.group(2))
        else:
            sleep_h, wake_h = 22, 6  # default

        if not (0 <= sleep_h < 24 and 0 <= wake_h < 24):
            sleep_h, wake_h = 22, 6

        if not self._demo:
            try:
                api = self.app.api  # type: ignore[attr-defined]
                await api.set_sleep_schedule(float(sleep_h), float(wake_h))
            except Exception as exc:
                await self._typewrite(f"Error: {exc}")
                self.app.switch_screen("dashboard")  # type: ignore[attr-defined]
                return

        name = self._creature_name or "Your creature"
        await self._spacer()
        await self._typewrite(
            f"{name} will sleep at {sleep_h}:00 and wake at {wake_h}:00.",
            css_class="narrative-accent",
        )
        await asyncio.sleep(2.0)

        await self._spacer()
        await self._typewrite(
            "It's vulnerable. New.",
            css_class="narrative-dim",
        )
        await asyncio.sleep(1.0)
        await self._typewrite(
            "It needs you.",
            css_class="narrative-dim",
        )
        await asyncio.sleep(2.5)

        if self._demo:
            await self._spacer()
            await self._typewrite(
                "[ demo complete — press q to quit ]",
                css_class="narrative-dim",
            )
        else:
            # Refresh app state and switch to dashboard
            await self.app._refresh_creature()  # type: ignore[attr-defined]
            self.app.switch_screen("dashboard")  # type: ignore[attr-defined]

    # ── Helpers ─────────────────────────────────────────────────────────

    def _scroll(self) -> VerticalScroll:
        return self.query_one("#hatch-scroll", VerticalScroll)

    def _scroll_down(self) -> None:
        self._scroll().scroll_end(animate=False)

    def _show_egg(self, stage: int) -> None:
        """Render egg art at a crack stage and append to scroll."""
        art = render_egg(self._palette_idx, stage)
        widget = Static(art, markup=True, classes="egg-art")
        self._scroll().mount(widget)
        self._scroll_down()

    async def _spacer(self) -> None:
        """Add vertical breathing room."""
        self._scroll().mount(Static("", classes="spacer"))
        self._scroll_down()

    async def _typewrite(
        self,
        text: str,
        *,
        css_class: str = "narrative",
        markup: bool = False,
    ) -> None:
        """Reveal text character by character with typewriter effect."""
        if markup:
            widget = Static("", markup=True)
        else:
            widget = Static("", classes=css_class)

        self._scroll().mount(widget)
        self._scroll_down()

        if markup:
            # For markup text, we need to reveal carefully to avoid
            # breaking tags mid-render. Reveal by segments outside tags.
            await self._reveal_markup(widget, text)
        else:
            # Plain text: reveal character by character
            buf = ""
            for ch in text:
                buf += ch
                widget.update(buf)
                if ch == "\n":
                    self._scroll_down()
                    await asyncio.sleep(_LINE_PAUSE)
                elif ch != " ":
                    await asyncio.sleep(_CHAR_DELAY)
            self._scroll_down()

    async def _reveal_markup(self, widget: Static, text: str) -> None:
        """Reveal Rich markup text progressively.

        Skips over markup tags so they aren't shown partially.
        """
        buf = ""
        i = 0
        while i < len(text):
            if text[i] == "[":
                # Find closing bracket — include whole tag at once
                end = text.find("]", i)
                if end != -1:
                    buf += text[i:end + 1]
                    i = end + 1
                    continue
            ch = text[i]
            buf += ch
            widget.update(buf)
            if ch == "\n":
                self._scroll_down()
                await asyncio.sleep(_LINE_PAUSE)
            elif ch != " ":
                await asyncio.sleep(_CHAR_DELAY)
            i += 1
        self._scroll_down()

    def refresh_from_creature(self, creature: object) -> None:
        pass
