from __future__ import annotations

from typing import Callable

from textual.app import ComposeResult
from textual.widgets import Static
from textual.widget import Widget

from django.db import models
from django.db.models import QuerySet

from django_shreck.messages import GoBack, ShowTable
from django_shreck.widgets.detail_widget import DetailWidget
from django_shreck.widgets.model_browser_widget import ModelBrowserWidget
from django_shreck.widgets.table_widget import TableWidget
from django_shreck.widgets.compare_widget import CompareWidget


class ResultPanel(Widget):
    """Container that switches between detail, table, compare, and command browser views.

    Listens for messages from the ShellWidget and mounts the appropriate child widget.

    Navigation stack
    ----------------
    Each entry is a zero-argument callable that rebuilds the view for that level.
    Pressing ESC pops the top entry and calls the previous one to restore it.
    The bottom of the stack is always the model browser.
    """

    DEFAULT_CSS = """
    ResultPanel {
        height: 100%;
        layout: vertical;
    }
    ResultPanel #result-placeholder {
        height: 100%;
        content-align: center middle;
        color: $text-muted;
    }
    """

    def __init__(self, **kwargs) -> None:
        super().__init__(**kwargs)
        self._current_view: Widget | None = None
        # Navigation stack: list of callables that restore a view.
        # Index 0 = deepest history, last index = current view.
        self._nav_stack: list[Callable[[], None]] = []

    def compose(self) -> ComposeResult:
        browser = ModelBrowserWidget(id="model-browser")
        self._current_view = browser
        # Seed the stack with the model-browser level
        self._nav_stack = [lambda: self._mount_model_browser()]
        yield browser

    def _clear_current(self) -> None:
        """Remove the currently displayed result widget."""
        if self._current_view is not None:
            self._current_view.remove()
            self._current_view = None
        placeholder = self.query("#result-placeholder")
        if placeholder:
            placeholder.remove()

    # ── internal mounters (no stack manipulation) ──────────────────────────

    def _mount_model_browser(self, focus_model=None) -> None:
        self._clear_current()
        widget = ModelBrowserWidget(focus_model=focus_model)
        self._current_view = widget
        self.mount(widget)

    def _mount_table(self, queryset: QuerySet) -> None:
        self._clear_current()
        widget = TableWidget(queryset=queryset)
        self._current_view = widget
        self.mount(widget)

    def _mount_detail(self, instance: models.Model, editable: bool = False, start_editing: bool = False) -> None:
        self._clear_current()
        widget = DetailWidget(instance=instance, editable=editable, start_editing=start_editing)
        self._current_view = widget
        self.mount(widget)

    def _mount_compare(self, instances: list) -> None:
        self._clear_current()
        widget = CompareWidget(instances=instances)
        self._current_view = widget
        self.mount(widget)

    def _mount_create(self, model) -> None:
        self._clear_current()
        new_instance = model()
        widget = DetailWidget(instance=new_instance, is_new=True)
        self._current_view = widget
        self.mount(widget)

    # ── public navigation methods (manage the stack) ───────────────────────

    def show_model_browser(self, focus_model=None) -> None:
        """Display the model browser and reset the nav stack."""
        self._nav_stack = [lambda fm=focus_model: self._mount_model_browser(fm)]
        self._mount_model_browser(focus_model)

    def show_table(self, queryset: QuerySet, push: bool = True) -> None:
        """Display a queryset table. push=True adds it on top of the current stack."""
        if push:
            self._nav_stack.append(lambda qs=queryset: self._mount_table(qs))
        self._mount_table(queryset)

    def show_detail(self, instance: models.Model, editable: bool = False, start_editing: bool = False) -> None:
        """Display a detail view, pushing it onto the nav stack."""
        self._nav_stack.append(
            lambda inst=instance, ed=editable, se=start_editing: self._mount_detail(inst, ed, se)
        )
        self._mount_detail(instance, editable, start_editing)

    def show_compare(self, instances: list) -> None:
        """Display a compare view, pushing it onto the nav stack."""
        self._nav_stack.append(lambda insts=instances: self._mount_compare(insts))
        self._mount_compare(instances)

    def show_create(self, model) -> None:
        """Display a blank create form, pushing it onto the nav stack."""
        self._nav_stack.append(lambda m=model: self._mount_create(m))
        self._mount_create(model)

    # ── message handlers ───────────────────────────────────────────────────

    def on_go_back(self, message: GoBack) -> None:
        """Pop the current view and restore the previous one."""
        message.stop()
        if len(self._nav_stack) > 1:
            self._nav_stack.pop()
            self._nav_stack[-1]()
        else:
            # Already at root (model browser); just re-mount it
            self._nav_stack[-1]()

    def on_show_table(self, message: ShowTable) -> None:
        """Handle ShowTable posted from child widgets (e.g. ModelBrowserWidget)."""
        message.stop()
        # If the current view is the model browser, update its stack entry to
        # include focus_model so ESC from the table restores cursor position.
        if isinstance(self._current_view, ModelBrowserWidget):
            focus_model = message.queryset.model
            self._nav_stack[-1] = lambda fm=focus_model: self._mount_model_browser(fm)
        self.show_table(message.queryset)

