import os
from functools import partial
from pathlib import Path

from loguru import logger

from .. import Keys
from ..Activity import Activity
from ..EventTypes import KeyStroke, ScrollChange, TextBoxSubmit
from ..input_handlers import handle_text_box_input, handle_scroll_list_input
from ..printers.TopBar import TopBar
from ..printers.TextInput import TextInput
from ..printers.HorizontalBar import HorizontalBar
from ..printers.printers import make_scroll_list
from ..printers.BottomBar import BottomBar


class FilePickerActivity(Activity):
    """Full-screen file/folder picker activity.

    Parameters
    ----------
    callback : callable
        Called with the selected path (str) or None on cancel.
    initial_path : str or None
        Starting directory. Defaults to user home.
    mode : str
        "select_folder" or "open_file".
    file_extensions : list[str] or None
        Filter for open_file mode, e.g. [".json", ".csv"].
    """

    def __init__(self, callback, initial_path=None, mode="select_folder", file_extensions=None):
        super().__init__()
        self.callback = callback
        self.mode = mode
        self.file_extensions = file_extensions
        self.tab_order = ["listing", "path_input"]
        self.focus = "listing"

        if initial_path and os.path.isdir(initial_path):
            self.current_dir = os.path.abspath(initial_path)
        else:
            self.current_dir = str(Path.home())

    def on_start(self):
        self.application.subscribe(KeyStroke, self, self.on_key)
        self.application.subscribe(TextBoxSubmit, self, self.on_path_submit)
        self.application.subscribe(ScrollChange, self, self.on_scroll_change)

        help_text = "ENTER select | TAB path | ESC cancel"
        title = "Select Folder" if self.mode == "select_folder" else "Open File"

        self.display_state = {
            "top": TopBar.display_state(items={"title": title, "help": help_text}),
            "path_input": TextInput.display_state(
                label="Path",
                text=self.current_dir,
                focused=False,
                input_handler=handle_text_box_input,
            ),
            "hr": HorizontalBar.display_state(),
            "listing": {
                "items": [],
                "selected_index": 0,
                "focused": True,
                "layout": {"flex": 1, "min_height": 5},
                "line_generator": partial(make_scroll_list, self.screen),
                "input_handler": handle_scroll_list_input,
            },
            "bottom_bar": BottomBar.display_state(items={"path": self.current_dir}),
        }

        self._populate_listing()
        self.refresh_screen()

    def _populate_listing(self):
        """Rebuild the listing items for self.current_dir."""
        items = []

        if self.mode == "select_folder":
            items.append(". (select this folder)")

        items.append("..")

        try:
            entries = sorted(os.listdir(self.current_dir), key=str.lower)
        except OSError as e:
            logger.error(f"Cannot list directory: {e}")
            entries = []

        dirs = []
        files = []
        for name in entries:
            if name.startswith("."):
                continue
            full = os.path.join(self.current_dir, name)
            if os.path.isdir(full):
                dirs.append(f"[DIR] {name}")
            else:
                if self.mode == "open_file":
                    if self.file_extensions:
                        _, ext = os.path.splitext(name)
                        if ext.lower() not in self.file_extensions:
                            continue
                    files.append(name)
                else:
                    files.append(name)

        items.extend(dirs)
        items.extend(files)

        ctx = self.display_state["listing"]
        ctx["items"] = items
        ctx["selected_index"] = 0

        self.display_state["path_input"]["text"] = self.current_dir
        self.display_state["path_input"]["cursor_index"] = len(self.current_dir)
        self.display_state["bottom_bar"]["items"]["path"] = self.current_dir

    def _navigate_to(self, path):
        """Change current_dir and refresh listing."""
        resolved = os.path.abspath(path)
        if os.path.isdir(resolved):
            self.current_dir = resolved
            self._populate_listing()
            self.refresh_screen()
        else:
            BottomBar.log_bottom_bar(self.display_state, f"Not a directory: {resolved}")
            self.refresh_screen()

    def _select_item(self):
        """Handle ENTER on the listing."""
        ctx = self.display_state["listing"]
        items = ctx["items"]
        if not items:
            return

        idx = max(0, min(int(ctx.get("selected_index", 0)), len(items) - 1))
        selected = items[idx]

        if selected == ". (select this folder)":
            self.callback(self.current_dir)
            self.application.pop_activity()
            return

        if selected == "..":
            self._navigate_to(os.path.dirname(self.current_dir))
            return

        if selected.startswith("[DIR] "):
            dir_name = selected[6:]
            self._navigate_to(os.path.join(self.current_dir, dir_name))
            return

        # It's a plain file
        if self.mode == "open_file":
            full_path = os.path.join(self.current_dir, selected)
            self.callback(full_path)
            self.application.pop_activity()

    def on_path_submit(self, event: TextBoxSubmit):
        if self.focus != "path_input":
            return
        text = self.display_state["path_input"].get("text", "").strip()
        if text:
            expanded = os.path.expanduser(text)
            self._navigate_to(expanded)

    def on_scroll_change(self, _event):
        ctx = self.display_state["listing"]
        items = ctx["items"]
        idx = int(ctx.get("selected_index", 0))
        if items and 0 <= idx < len(items):
            selected = items[idx]
            if selected.startswith("[DIR] "):
                dir_name = selected[6:]
                full = os.path.join(self.current_dir, dir_name)
            elif selected == "..":
                full = os.path.dirname(self.current_dir)
            elif selected == ". (select this folder)":
                full = self.current_dir
            else:
                full = os.path.join(self.current_dir, selected)
            self.display_state["bottom_bar"]["items"]["path"] = full
        self.refresh_screen()

    def on_key(self, event: KeyStroke):
        key = event.key

        if key == Keys.ESC:
            self.callback(None)
            self.application.pop_activity()
            return

        if key == Keys.TAB:
            self.cycle_focus()
            self.refresh_screen()
            return

        if key == Keys.ENTER and self.focus == "listing":
            self._select_item()
            return

        self.delegate_to_focused(event)

        self.refresh_screen()
