#!/usr/bin/env python3
# Familiar - Self-Hosted AI Companion Platform
# Copyright (c) 2026 George Scott Foley
# ORCID: 0009-0006-4957-0540
# Email: Georgescottfoley@proton.me
# Licensed under the MIT License - see LICENSE file for details

"""
Familiar Onboarding TUI — Textual terminal GUI.

A rich, keyboard-navigable terminal wizard that guides non-technical
users through setup.  Delegates all business logic to OnboardingEngine.

Usage::

    python -m familiar.onboard_tui
    python -m familiar --onboard-tui
"""

try:
    from textual.app import App, ComposeResult
    from textual.binding import Binding
    from textual.containers import Center, Horizontal, Vertical, VerticalScroll
    from textual.screen import Screen
    from textual.widgets import (
        Button,
        Checkbox,
        Footer,
        Header,
        Input,
        Log,
        ProgressBar,
        RadioButton,
        RadioSet,
        Static,
        Switch,
    )
except ImportError:
    import sys

    print("Textual TUI requires the 'textual' package.")
    print("Install with:  pip install 'familiar-agent[tui]'")
    print("Or directly:   pip install 'textual>=0.70'")
    sys.exit(1)

from familiar.onboard_engine import (
    CHANNELS,
    PERSONA_LABELS,
    PROVIDERS,
    OnboardingEngine,
)

# ── Screens ───────────────────────────────────────────────────────────


class WelcomeScreen(Screen):
    """Step 0: Welcome."""

    def compose(self) -> ComposeResult:
        yield Header()
        with Center():
            with Vertical(id="welcome-box"):
                yield Static(
                    "\n  F A M I L I A R\n\n"
                    "  An AI companion designed to take care of you.\n\n"
                    "  This wizard will set up your personal AI assistant.\n"
                    "  You'll choose an AI provider, communication channel,\n"
                    "  and give your assistant a name and personality.\n",
                    id="welcome-text",
                )
                yield Button("Get Started", variant="primary", id="btn-start")
        yield Footer()

    def on_button_pressed(self, event: Button.Pressed) -> None:
        if event.button.id == "btn-start":
            self.app.action_next_step()


class ProviderScreen(Screen):
    """Step 1: LLM Provider selection."""

    def compose(self) -> ComposeResult:
        yield Header()
        with VerticalScroll():
            yield Static("\n  Step 1 of 7: AI Provider\n", classes="step-title")
            with RadioSet(id="provider-radio"):
                for p in PROVIDERS:
                    detected = self.app.detected_providers.get(p["key"])
                    label = p["label"]
                    if detected:
                        label += " (detected)"
                    yield RadioButton(label, value=p["key"] == "anthropic")
            yield Static("", id="provider-hint")
            yield Input(placeholder="Paste API key here", password=True, id="api-key-input")
            # Ollama model picker section
            yield Static("", id="ollama-section-label")
            with Vertical(id="ollama-models"):
                with RadioSet(id="ollama-mode-radio"):
                    yield RadioButton("Recommended Bundle", value=True)
                    yield RadioButton("Custom")
                    yield RadioButton("Skip (manage manually)")
                yield Static("", id="ollama-bundle-hint")
            yield Static("", id="provider-error", classes="error")
            with Horizontal(classes="btn-row"):
                yield Button("Back", id="btn-back")
                yield Button("Next", variant="primary", id="btn-next")
        yield Footer()

    def on_mount(self) -> None:
        detected_keys = list(self.app.detected_providers.keys())
        radio = self.query_one("#provider-radio", RadioSet)
        if detected_keys:
            for i, p in enumerate(PROVIDERS):
                if p["key"] in detected_keys:
                    radio.index = i
                    break
        self._update_key_visibility()

    def on_radio_set_changed(self, event: RadioSet.Changed) -> None:
        if event.radio_set.id == "provider-radio":
            self._update_key_visibility()

    def _update_key_visibility(self) -> None:
        radio = self.query_one("#provider-radio", RadioSet)
        idx = radio.index if radio.index is not None else 0
        provider_key = PROVIDERS[idx]["key"]
        key_input = self.query_one("#api-key-input", Input)
        hint = self.query_one("#provider-hint", Static)
        ollama_section = self.query_one("#ollama-models", Vertical)
        ollama_label = self.query_one("#ollama-section-label", Static)

        if provider_key == "ollama":
            key_input.display = False
            hint.update("  No API key needed — runs locally on your machine.")
            ollama_section.display = True
            ollama_label.update("\n  Model Setup:")
            # Show bundle recommendation
            bundles = self.app.engine.get_ollama_bundles()
            rec = bundles["recommended"]
            self.query_one("#ollama-bundle-hint", Static).update(
                f"  {rec['label']}: {rec['desc']}\n  ({bundles['ram_gb']:.0f} GB RAM detected)"
            )
        else:
            ollama_section.display = False
            ollama_label.update("")
            key_input.display = True
            detected = self.app.detected_providers.get(provider_key)
            if detected and detected.get("key"):
                key_input.value = detected["key"]
                hint.update("  API key detected from environment.")
            else:
                url = PROVIDERS[idx].get("url", "")
                hint.update(f"  Get a key at: {url}")

    def on_button_pressed(self, event: Button.Pressed) -> None:
        if event.button.id == "btn-back":
            self.app.action_prev_step()
        elif event.button.id == "btn-next":
            self._validate()

    def _validate(self) -> None:
        radio = self.query_one("#provider-radio", RadioSet)
        idx = radio.index if radio.index is not None else 0
        provider_key = PROVIDERS[idx]["key"]
        api_key = self.query_one("#api-key-input", Input).value.strip()

        data = {"provider": provider_key, "api_key": api_key}

        # Handle Ollama model selection
        if provider_key == "ollama":
            mode_radio = self.query_one("#ollama-mode-radio", RadioSet)
            mode_idx = mode_radio.index if mode_radio.index is not None else 0
            bundles = self.app.engine.get_ollama_bundles()
            rec = bundles["recommended"]

            if mode_idx == 0:  # Recommended
                models = rec["models"]
                data["ollama_model"] = models[0]
                if len(models) >= 3:
                    data["lightweight_model"] = models[1]
            elif mode_idx == 1:  # Custom — use first recommended as default
                data["ollama_model"] = rec["models"][0]
            # mode_idx == 2: Skip — no model assignment

        result = self.app.engine.validate_provider(data)

        error_label = self.query_one("#provider-error", Static)
        if result.success:
            error_label.update("")
            self.app.action_next_step()
        else:
            error_label.update("  " + ". ".join(result.errors))


class ChannelScreen(Screen):
    """Step 2: Channel selection — multi-select with checkboxes."""

    def compose(self) -> ComposeResult:
        yield Header()
        with VerticalScroll():
            yield Static("\n  Step 2 of 7: Communication Channels\n", classes="step-title")
            yield Static("  Select one or more channels (first = primary):\n")
            with Vertical(id="channel-checkboxes"):
                for ch in CHANNELS:
                    yield Checkbox(ch["label"], id=f"ch-{ch['key']}")
            # Per-channel token inputs for Telegram / Discord
            yield Static("  Telegram bot token:", id="telegram-label-token")
            yield Input(
                placeholder="123456:ABCdef...",
                password=True,
                id="telegram-token",
            )
            yield Static("  Discord bot token:", id="discord-label-token")
            yield Input(
                placeholder="MTk...",
                password=True,
                id="discord-token",
            )
            # Matrix-specific fields
            yield Static("  Homeserver URL:", id="matrix-label-hs")
            yield Input(placeholder="https://matrix.org", id="matrix-homeserver")
            yield Static("  Bot user ID:", id="matrix-label-user")
            yield Input(placeholder="@familiar:matrix.org", id="matrix-user")
            yield Static("  Matrix auth method:", id="matrix-auth-label")
            with RadioSet(id="matrix-auth-radio"):
                yield RadioButton("Password", value=True)
                yield RadioButton("Access Token")
            yield Static("  Password:", id="matrix-label-pw")
            yield Input(placeholder="Password", password=True, id="matrix-password")
            yield Static("  Access Token:", id="matrix-label-token")
            yield Input(placeholder="Access token", password=True, id="matrix-access-token")
            # Signal-specific fields
            yield Static("  Signal phone number:", id="signal-label-phone")
            yield Input(placeholder="+14155551234", id="signal-phone")
            # WhatsApp-specific fields
            yield Static("  WhatsApp bridge port:", id="whatsapp-label-port")
            yield Input(placeholder="3001", value="3001", id="whatsapp-port")
            # iMessage info
            yield Static(
                "  iMessage requires macOS with Messages.app.\n"
                "  No additional configuration needed.",
                id="imessage-label-info",
            )
            # Teams-specific fields
            yield Static("  Teams App ID:", id="teams-label-appid")
            yield Input(placeholder="Azure Bot App ID", id="teams-app-id")
            yield Static("  Teams App Password:", id="teams-label-pw")
            yield Input(
                placeholder="App password from Azure",
                password=True,
                id="teams-app-password",
            )
            yield Static("", id="channel-error", classes="error")
            with Horizontal(classes="btn-row"):
                yield Button("Back", id="btn-back")
                yield Button("Next", variant="primary", id="btn-next")
        yield Footer()

    def on_mount(self) -> None:
        self._update_field_visibility()

    def on_checkbox_changed(self, event: Checkbox.Changed) -> None:
        self._update_field_visibility()

    def on_radio_set_changed(self, event: RadioSet.Changed) -> None:
        if event.radio_set.id == "matrix-auth-radio":
            self._update_matrix_auth()

    def _get_selected_channels(self) -> list:
        selected = []
        for ch in CHANNELS:
            cb = self.query_one(f"#ch-{ch['key']}", Checkbox)
            if cb.value:
                selected.append(ch["key"])
        return selected

    def _update_field_visibility(self) -> None:
        selected = self._get_selected_channels()

        # Per-channel token inputs
        show_telegram = "telegram" in selected
        for wid in ("#telegram-label-token", "#telegram-token"):
            self.query_one(wid).display = show_telegram
        show_discord = "discord" in selected
        for wid in ("#discord-label-token", "#discord-token"):
            self.query_one(wid).display = show_discord

        # Matrix fields
        show_matrix = "matrix" in selected
        for wid in (
            "#matrix-label-hs",
            "#matrix-homeserver",
            "#matrix-label-user",
            "#matrix-user",
            "#matrix-auth-label",
            "#matrix-auth-radio",
        ):
            self.query_one(wid).display = show_matrix
        if show_matrix:
            self._update_matrix_auth()
        else:
            for wid in (
                "#matrix-label-pw",
                "#matrix-password",
                "#matrix-label-token",
                "#matrix-access-token",
            ):
                self.query_one(wid).display = False

        # Signal
        show_signal = "signal" in selected
        for wid in ("#signal-label-phone", "#signal-phone"):
            self.query_one(wid).display = show_signal

        # WhatsApp
        show_whatsapp = "whatsapp" in selected
        for wid in ("#whatsapp-label-port", "#whatsapp-port"):
            self.query_one(wid).display = show_whatsapp

        # iMessage
        self.query_one("#imessage-label-info").display = "imessage" in selected

        # Teams
        show_teams = "teams" in selected
        for wid in (
            "#teams-label-appid",
            "#teams-app-id",
            "#teams-label-pw",
            "#teams-app-password",
        ):
            self.query_one(wid).display = show_teams

    def _update_matrix_auth(self) -> None:
        radio = self.query_one("#matrix-auth-radio", RadioSet)
        idx = radio.index if radio.index is not None else 0
        use_password = idx == 0
        self.query_one("#matrix-label-pw", Static).display = use_password
        self.query_one("#matrix-password", Input).display = use_password
        self.query_one("#matrix-label-token", Static).display = not use_password
        self.query_one("#matrix-access-token", Input).display = not use_password

    def on_button_pressed(self, event: Button.Pressed) -> None:
        if event.button.id == "btn-back":
            self.app.action_prev_step()
        elif event.button.id == "btn-next":
            self._validate()

    def _validate(self) -> None:
        selected = self._get_selected_channels()
        data = {"channels": selected}

        if "telegram" in selected:
            data["telegram_token"] = self.query_one("#telegram-token", Input).value.strip()
        if "discord" in selected:
            data["discord_token"] = self.query_one("#discord-token", Input).value.strip()
        if "matrix" in selected:
            data["matrix_homeserver"] = self.query_one("#matrix-homeserver", Input).value.strip()
            data["matrix_user"] = self.query_one("#matrix-user", Input).value.strip()
            auth_radio = self.query_one("#matrix-auth-radio", RadioSet)
            auth_idx = auth_radio.index if auth_radio.index is not None else 0
            if auth_idx == 0:
                data["matrix_password"] = self.query_one("#matrix-password", Input).value.strip()
            else:
                data["matrix_access_token"] = self.query_one(
                    "#matrix-access-token", Input
                ).value.strip()
        if "signal" in selected:
            data["signal_phone"] = self.query_one("#signal-phone", Input).value.strip()
        if "whatsapp" in selected:
            data["whatsapp_port"] = self.query_one("#whatsapp-port", Input).value.strip()
        if "teams" in selected:
            data["teams_app_id"] = self.query_one("#teams-app-id", Input).value.strip()
            data["teams_app_password"] = self.query_one("#teams-app-password", Input).value.strip()

        result = self.app.engine.validate_channels(data)
        error_label = self.query_one("#channel-error", Static)
        if result.success:
            error_label.update("")
            self.app.action_next_step()
        else:
            error_label.update("  " + ". ".join(result.errors))


class IdentityScreen(Screen):
    """Step 3: Name and persona."""

    def compose(self) -> ComposeResult:
        yield Header()
        with VerticalScroll():
            yield Static("\n  Step 3 of 7: Name Your Assistant\n", classes="step-title")
            yield Static("  Assistant name:")
            yield Input(value="Familiar", id="name-input")
            yield Static("\n  Persona style:\n")
            with RadioSet(id="persona-radio"):
                for key, label in PERSONA_LABELS.items():
                    yield RadioButton(label, value=key == "hospitality")
            yield Static("", id="identity-error", classes="error")
            with Horizontal(classes="btn-row"):
                yield Button("Back", id="btn-back")
                yield Button("Next", variant="primary", id="btn-next")
        yield Footer()

    def on_button_pressed(self, event: Button.Pressed) -> None:
        if event.button.id == "btn-back":
            self.app.action_prev_step()
        elif event.button.id == "btn-next":
            self._validate()

    def _validate(self) -> None:
        name = self.query_one("#name-input", Input).value.strip()
        radio = self.query_one("#persona-radio", RadioSet)
        idx = radio.index if radio.index is not None else 0
        persona_keys = list(PERSONA_LABELS.keys())
        persona = persona_keys[idx]

        result = self.app.engine.validate_identity({"name": name, "persona": persona})
        error_label = self.query_one("#identity-error", Static)
        if result.success:
            error_label.update("")
            self.app.action_next_step()
        else:
            error_label.update("  " + ". ".join(result.errors))


class SecurityScreen(Screen):
    """Step 4: PIN and encryption."""

    def compose(self) -> ComposeResult:
        yield Header()
        with VerticalScroll():
            yield Static("\n  Step 4 of 7: Security\n", classes="step-title")
            yield Static("  Owner PIN (4-8 digits):")
            yield Input(
                value=OnboardingEngine.generate_default_pin(),
                id="pin-input",
                max_length=8,
            )
            yield Static(
                "  This PIN lets you claim ownership when messaging your\n"
                "  bot for the first time. Send it as your first message\n"
                "  after /start.\n"
            )
            with Horizontal():
                yield Static("  Encrypt data at rest  ")
                yield Switch(value=True, id="encrypt-switch")
            yield Input(
                placeholder="Encryption passphrase (leave blank for auto)",
                id="passphrase-input",
            )
            yield Static("", id="security-error", classes="error")
            with Horizontal(classes="btn-row"):
                yield Button("Back", id="btn-back")
                yield Button("Next", variant="primary", id="btn-next")
        yield Footer()

    def on_mount(self) -> None:
        self.query_one("#passphrase-input", Input).display = True

    def on_switch_changed(self, event: Switch.Changed) -> None:
        self.query_one("#passphrase-input", Input).display = event.value

    def on_button_pressed(self, event: Button.Pressed) -> None:
        if event.button.id == "btn-back":
            self.app.action_prev_step()
        elif event.button.id == "btn-next":
            self._validate()

    def _validate(self) -> None:
        pin = self.query_one("#pin-input", Input).value.strip()
        encrypt = self.query_one("#encrypt-switch", Switch).value
        passphrase = self.query_one("#passphrase-input", Input).value.strip()

        pin_result = self.app.engine.validate_owner_pin({"pin": pin})
        error_label = self.query_one("#security-error", Static)
        if not pin_result.success:
            error_label.update("  " + ". ".join(pin_result.errors))
            return

        self.app.engine.validate_encryption({"enabled": encrypt, "passphrase": passphrase})
        error_label.update("")
        self.app.action_next_step()


class BriefingScreen(Screen):
    """Step 5: Briefing settings."""

    def compose(self) -> ComposeResult:
        yield Header()
        with VerticalScroll():
            yield Static("\n  Step 5 of 7: Morning Briefing\n", classes="step-title")
            yield Static(
                "  Familiar can send you a daily briefing with your\n"
                "  calendar, tasks, and anything needing attention.\n"
            )
            with Horizontal():
                yield Static("  Enable daily briefing  ")
                yield Switch(value=True, id="briefing-switch")
            yield Static("  Briefing time (HH:MM):")
            yield Input(value="08:00", id="time-input")
            with Horizontal():
                yield Static("  Proactive check-ins  ")
                yield Switch(value=True, id="heartbeat-switch")
            yield Static(
                "\n  Briefing works best with Google Calendar.\n"
                "  Run: python -m familiar --setup-google\n",
                classes="hint-text",
            )
            yield Static("", id="briefing-error", classes="error")
            with Horizontal(classes="btn-row"):
                yield Button("Back", id="btn-back")
                yield Button("Next", variant="primary", id="btn-next")
        yield Footer()

    def on_button_pressed(self, event: Button.Pressed) -> None:
        if event.button.id == "btn-back":
            self.app.action_prev_step()
        elif event.button.id == "btn-next":
            self._validate()

    def _validate(self) -> None:
        enabled = self.query_one("#briefing-switch", Switch).value
        time_str = self.query_one("#time-input", Input).value.strip()
        heartbeat = self.query_one("#heartbeat-switch", Switch).value

        result = self.app.engine.validate_briefing(
            {
                "enabled": enabled,
                "time": time_str,
                "heartbeat": heartbeat,
            }
        )

        error_label = self.query_one("#briefing-error", Static)
        if result.success:
            error_label.update("")
            self.app.action_next_step()
        else:
            error_label.update("  " + ". ".join(result.errors))


class ReviewScreen(Screen):
    """Step 6: Review and activate."""

    def compose(self) -> ComposeResult:
        yield Header()
        with VerticalScroll():
            yield Static("\n  Step 6 of 7: Review\n", classes="step-title")
            yield Static("", id="summary-text")
            with Horizontal(classes="btn-row"):
                yield Button("Back", id="btn-back")
                yield Button("Activate", variant="primary", id="btn-activate")
        yield Footer()

    def on_screen_resume(self) -> None:
        self._build_summary()

    def on_mount(self) -> None:
        self._build_summary()

    def _build_summary(self) -> None:
        summary = self.app.engine.get_summary()
        lines = [
            f"  Provider:    {summary['provider']}",
        ]
        if summary.get("ollama_model"):
            lines.append(f"  Main model:  {summary['ollama_model']}")
        if summary.get("lightweight_model"):
            lines.append(f"  Background:  {summary['lightweight_model']}")
        lines.extend(
            [
                f"  Channels:    {', '.join(summary['channels'])}",
                f"  Name:        {summary['name']}",
                f"  Persona:     {summary['persona_key']}",
                f"  PIN:         {'*' * summary['pin_length']} ({summary['pin_length']} digits)",
                f"  Encryption:  {'enabled' if summary['encrypt_at_rest'] else 'off'}",
            ]
        )
        if summary["briefing_enabled"]:
            lines.append(f"  Briefing:    daily at {summary['briefing_time']}")
            if summary["heartbeat_enabled"]:
                lines.append("  Check-ins:   every 4 hours")
        else:
            lines.append("  Briefing:    off")
        self.query_one("#summary-text", Static).update("\n".join(lines) + "\n")

    def on_button_pressed(self, event: Button.Pressed) -> None:
        if event.button.id == "btn-back":
            self.app.action_prev_step()
        elif event.button.id == "btn-activate":
            self.app.action_next_step()


class ActivationScreen(Screen):
    """Step 7: Activation with progress."""

    def compose(self) -> ComposeResult:
        yield Header()
        with VerticalScroll():
            yield Static("\n  Step 7 of 7: Activating\n", classes="step-title")
            yield ProgressBar(total=100, id="progress")
            yield Log(id="activation-log", auto_scroll=True)
            yield Static("", id="complete-text")
            yield Button("Done", variant="primary", id="btn-done")
        yield Footer()

    def on_mount(self) -> None:
        self.query_one("#btn-done", Button).display = False
        self.query_one("#complete-text", Static).update("")
        self._run_activation()

    def _run_activation(self) -> None:
        log = self.query_one("#activation-log", Log)
        progress = self.query_one("#progress", ProgressBar)
        progress.update(progress=10)

        import threading

        def _activate():
            step_count = [0]

            def _notify(msg):
                step_count[0] += 1
                p = min(10 + step_count[0] * 12, 95)
                self.call_from_thread(progress.update, progress=p)
                self.call_from_thread(log.write_line, f"  > {msg}")

            result = self.app.engine.activate(notify=_notify)

            if result.success:
                # Send test message
                test_result = self.app.engine.send_test_message(notify=_notify)
                for w in test_result.warnings:
                    self.call_from_thread(log.write_line, f"  ! {w}")

            self.call_from_thread(progress.update, progress=100)
            summary = self.app.engine.get_summary()

            if result.success:
                # Build summary + checklist
                checklist = self.app.engine.get_post_activation_checklist()
                lines = [f"\n  {summary['name']} is ready!\n"]
                lines.append("  Next steps:\n")
                for i, item in enumerate(checklist, 1):
                    lines.append(f"  {i}. {item['title']}")
                    lines.append(f"     {item['detail']}")
                lines.append("")

                self.call_from_thread(
                    self.query_one("#complete-text", Static).update,
                    "\n".join(lines),
                )
                self.app.engine.clear_saved_state()
            else:
                self.call_from_thread(
                    self.query_one("#complete-text", Static).update,
                    f"\n  Activation failed: {'; '.join(result.errors)}\n",
                )
            self.call_from_thread(self._show_done)

        t = threading.Thread(target=_activate, daemon=True)
        t.start()

    def _show_done(self) -> None:
        self.query_one("#btn-done", Button).display = True

    def on_button_pressed(self, event: Button.Pressed) -> None:
        if event.button.id == "btn-done":
            self.app.exit()


# ── Main App ──────────────────────────────────────────────────────────


class OnboardTUI(App):
    """Familiar Onboarding — Terminal UI."""

    TITLE = "Familiar Setup"
    CSS = """
    Screen {
        background: $surface;
    }
    #welcome-box {
        width: 60;
        height: auto;
        padding: 2 4;
    }
    #welcome-text {
        text-align: center;
    }
    .step-title {
        text-style: bold;
    }
    .error {
        color: $error;
    }
    .hint-text {
        color: $text-muted;
    }
    .btn-row {
        margin-top: 2;
        height: 3;
    }
    """
    BINDINGS = [
        Binding("q", "quit", "Quit"),
    ]

    SCREENS = {
        "welcome": WelcomeScreen,
        "provider": ProviderScreen,
        "channel": ChannelScreen,
        "identity": IdentityScreen,
        "security": SecurityScreen,
        "briefing": BriefingScreen,
        "review": ReviewScreen,
        "activation": ActivationScreen,
    }

    def __init__(self):
        super().__init__()
        self.engine = OnboardingEngine()
        self.detected_providers = {}
        self._step_order = [
            "welcome",
            "provider",
            "channel",
            "identity",
            "security",
            "briefing",
            "review",
            "activation",
        ]
        self._current_step = 0
        self._resume_step = 0

    def on_mount(self) -> None:
        result = self.engine.detect_providers()
        self.detected_providers = result.data.get("providers", {})

        # Check for saved state to resume
        saved = self.engine.load_saved_state()
        if saved:
            self.engine._state = saved.get("state", {})
            self._resume_step = saved.get("step", 0)
            # Skip directly to the saved step
            self._current_step = self._resume_step
            self.push_screen(self._step_order[self._current_step])
        else:
            self.push_screen("welcome")

    def action_next_step(self) -> None:
        self._current_step += 1
        if self._current_step < len(self._step_order):
            # Save progress after each step (skip welcome/activation)
            if 0 < self._current_step < len(self._step_order) - 1:
                self.engine.save_state(self._current_step)
            self.switch_screen(self._step_order[self._current_step])

    def action_prev_step(self) -> None:
        if self._current_step > 0:
            self._current_step -= 1
            self.switch_screen(self._step_order[self._current_step])


# ── Entry Point ───────────────────────────────────────────────────────


def main():
    app = OnboardTUI()
    app.run()


if __name__ == "__main__":
    main()
