"""
Content display widget for the EPUB reader.
"""
import re
from typing import Dict, List, Optional, Tuple

from rich.text import Text
from textual.binding import Binding
from textual.widgets import Static

from speakub.utils.text_utils import word_wrap


class ViewportContent:
    """Manages viewport-based content structure with dynamic sizing and logical line navigation."""

    __slots__ = (
        'content_lines', 'viewport_height', 'total_lines', 'total_pages',
        'current_page', 'cursor_in_page', 'content_line_indices',
        'line_to_content_index', 'total_content_lines', 'line_to_paragraph_map',
        'paragraphs', 'logical_lines', 'line_to_logical'
    )

    def __init__(self, content_lines: List[str], viewport_height: int = 25):
        self.content_lines = content_lines
        self.viewport_height = viewport_height
        self.total_lines = len(content_lines)
        self.total_pages = max(
            1, (self.total_lines + viewport_height - 1) // viewport_height
        )
        self.current_page = 0
        self.cursor_in_page = 0
        self._build_logical_line_map()
        self._build_content_line_map()
        self._build_paragraph_map()

    def _is_content_line(self, line: str) -> bool:
        return bool(line and line.replace("&nbsp;", "").strip())

    def _build_content_line_map(self):
        self.content_line_indices = []
        self.line_to_content_index = {}
        content_index = 0
        for line_idx, line in enumerate(self.content_lines):
            if self._is_content_line(line):
                self.content_line_indices.append(line_idx)
                self.line_to_content_index[line_idx] = content_index
                content_index += 1
        self.total_content_lines = len(self.content_line_indices)

    def _build_paragraph_map(self):
        self.line_to_paragraph_map = {}
        self.paragraphs = []
        current_paragraph_lines = []
        paragraph_idx = 0
        for line_idx, line in enumerate(self.content_lines):
            if self._is_content_line(line):
                current_paragraph_lines.append(line_idx)
            else:
                if current_paragraph_lines:
                    para_info = {
                        "start": current_paragraph_lines[0],
                        "end": current_paragraph_lines[-1],
                        "lines": current_paragraph_lines,
                        "index": paragraph_idx,
                    }
                    self.paragraphs.append(para_info)
                    for p_line_idx in current_paragraph_lines:
                        self.line_to_paragraph_map[p_line_idx] = para_info
                    paragraph_idx += 1
                    current_paragraph_lines = []
        if current_paragraph_lines:
            para_info = {
                "start": current_paragraph_lines[0],
                "end": current_paragraph_lines[-1],
                "lines": current_paragraph_lines,
                "index": paragraph_idx,
            }
            self.paragraphs.append(para_info)
            for p_line_idx in current_paragraph_lines:
                self.line_to_paragraph_map[p_line_idx] = para_info

    def get_paragraph_text(self, para_info: dict) -> str:
        # Step 1: This is the most important part of the original code,
        # it builds the para_lines list from para_info.
        # This is the part that was missing in your current version.
        para_lines = []
        for line_idx in para_info["lines"]:
            if line_idx < len(self.content_lines):
                line_text = self.content_lines[line_idx].strip()
                if line_text:
                    para_lines.append(line_text)

        # If para_lines is empty, return an empty string directly to avoid errors.
        if not para_lines:
            return ""

        # Step 2: This is the "perfect solution" logic I suggested before,
        # but now it can safely handle the para_lines list that has been
        # correctly created in the previous step.
        result = ""
        for i, line in enumerate(para_lines):
            result += line
            # If not the last line
            if i < len(para_lines) - 1:
                # Determine if a space needs to be added between lines
                # (handling English word breaks)
                # Rule: If the current line ends with an English letter and
                # the next line starts with an English letter, add a space
                if line.endswith(('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z')) and para_lines[i+1].startswith(('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z')):
                    result += " "

        return result

    def _find_next_content_line(self, current_global_pos: int) -> Optional[int]:
        for content_line_idx in self.content_line_indices:
            if content_line_idx > current_global_pos:
                return content_line_idx
        return None

    def _find_prev_content_line(self, current_global_pos: int) -> Optional[int]:
        for content_line_idx in reversed(self.content_line_indices):
            if content_line_idx < current_global_pos:
                return content_line_idx
        return None

    def update_viewport_height(self, new_height: int):
        if new_height > 0 and new_height != self.viewport_height:
            current_global_pos = self.get_cursor_global_position()
            self.viewport_height = new_height
            self.total_pages = max(1, (self.total_lines + new_height - 1) // new_height)
            new_page = current_global_pos // new_height
            new_cursor = current_global_pos % new_height
            self.current_page = min(new_page, self.total_pages - 1)
            viewport_lines = len(self.get_current_viewport_lines())
            self.cursor_in_page = min(new_cursor, max(0, viewport_lines - 1))

    def _build_logical_line_map(self):
        self.logical_lines = []
        self.line_to_logical = {}
        current_paragraph = []
        logical_idx = 0
        for i, line in enumerate(self.content_lines):
            if not self._is_content_line(line):
                if current_paragraph:
                    self.logical_lines.append(
                        {
                            "lines": current_paragraph,
                            "start_line": current_paragraph[0],
                            "end_line": current_paragraph[-1],
                            "is_paragraph": True,
                        }
                    )
                    for line_idx in current_paragraph:
                        self.line_to_logical[line_idx] = logical_idx
                    logical_idx += 1
                    current_paragraph = []
            else:
                current_paragraph.append(i)
        if current_paragraph:
            self.logical_lines.append(
                {
                    "lines": current_paragraph,
                    "start_line": current_paragraph[0],
                    "end_line": current_paragraph[-1],
                    "is_paragraph": True,
                }
            )
            for line_idx in current_paragraph:
                self.line_to_logical[line_idx] = logical_idx

    def get_current_viewport_lines(self) -> List[str]:
        start_idx = self.current_page * self.viewport_height
        end_idx = min(start_idx + self.viewport_height, self.total_lines)
        return self.content_lines[start_idx:end_idx]

    def get_cursor_global_position(self) -> int:
        return self.current_page * self.viewport_height + self.cursor_in_page

    def get_viewport_info(self) -> Dict[str, int]:
        return {
            "current_page": self.current_page,
            "total_pages": self.total_pages,
            "cursor_in_page": self.cursor_in_page,
            "lines_in_current_viewport": len(self.get_current_viewport_lines()),
            "global_cursor": self.get_cursor_global_position(),
            "viewport_height": self.viewport_height,
            "total_content_lines": self.total_content_lines,
        }

    def move_cursor_down(self) -> Tuple[bool, bool]:
        current_global_pos = self.get_cursor_global_position()
        current_logical_idx = self.line_to_logical.get(current_global_pos)
        if current_logical_idx is None:
            return self._move_cursor_down_content_fallback()
        next_logical_idx = current_logical_idx + 1
        if next_logical_idx >= len(self.logical_lines):
            return False, True
        next_logical = self.logical_lines[next_logical_idx]
        next_line_pos = next_logical["start_line"]
        next_page = next_line_pos // self.viewport_height
        next_cursor = next_line_pos % self.viewport_height
        page_changed = self.current_page != next_page
        self.current_page = next_page
        self.cursor_in_page = next_cursor
        return page_changed, False

    def move_cursor_up(self) -> Tuple[bool, bool]:
        current_global_pos = self.get_cursor_global_position()
        current_logical_idx = self.line_to_logical.get(current_global_pos)
        if current_logical_idx is None:
            return self._move_cursor_up_content_fallback()
        prev_logical_idx = current_logical_idx - 1
        if prev_logical_idx < 0:
            return False, True
        prev_logical = self.logical_lines[prev_logical_idx]
        prev_line_pos = prev_logical["start_line"]
        prev_page = prev_line_pos // self.viewport_height
        prev_cursor = prev_line_pos % self.viewport_height
        page_changed = self.current_page != prev_page
        self.current_page = prev_page
        self.cursor_in_page = prev_cursor
        return page_changed, False

    def _move_cursor_down_content_fallback(self) -> Tuple[bool, bool]:
        current_global_pos = self.get_cursor_global_position()
        next_content_line = self._find_next_content_line(current_global_pos)
        if next_content_line is None:
            return False, True
        next_page = next_content_line // self.viewport_height
        next_cursor = next_content_line % self.viewport_height
        return self._change_to_page(next_page, next_cursor), False

    def _move_cursor_up_content_fallback(self) -> Tuple[bool, bool]:
        current_global_pos = self.get_cursor_global_position()
        prev_content_line = self._find_prev_content_line(current_global_pos)
        if prev_content_line is None:
            return False, True
        prev_page = prev_content_line // self.viewport_height
        prev_cursor = prev_content_line % self.viewport_height
        return self._change_to_page(prev_page, prev_cursor), False

    def _change_to_page(self, page_num: int, cursor_pos: int) -> bool:
        page_changed = self.current_page != page_num
        if 0 <= page_num < self.total_pages:
            self.current_page = page_num
            page_lines = len(self.get_current_viewport_lines())
            self.cursor_in_page = max(0, min(cursor_pos, page_lines - 1))
            return page_changed
        return False

    def jump_to_page(self, page_num: int) -> bool:
        return self._change_to_page(page_num, 0)

    def page_down(self) -> Tuple[bool, bool]:
        if self.current_page < self.total_pages - 1:
            next_page = self.current_page + 1
            start_idx = next_page * self.viewport_height
            end_idx = min(start_idx + self.viewport_height, self.total_lines)
            first_content_cursor = 0
            for line_idx in range(start_idx, end_idx):
                if line_idx < len(self.content_lines) and self._is_content_line(
                    self.content_lines[line_idx]
                ):
                    first_content_cursor = line_idx - start_idx
                    break
            self._change_to_page(next_page, first_content_cursor)
            return True, False
        else:
            return False, True

    def page_up(self) -> Tuple[bool, bool]:
        if self.current_page > 0:
            prev_page = self.current_page - 1
            start_idx = prev_page * self.viewport_height
            end_idx = min(start_idx + self.viewport_height, self.total_lines)
            first_content_cursor = 0
            for line_idx in range(start_idx, end_idx):
                if line_idx < len(self.content_lines) and self._is_content_line(
                    self.content_lines[line_idx]
                ):
                    first_content_cursor = line_idx - start_idx
                    break
            self._change_to_page(prev_page, first_content_cursor)
            return True, False
        else:
            return False, True


class ContentDisplay(Static):
    BINDINGS = [
        Binding("up", "cursor_up", "Cursor Up", show=False),
        Binding("down", "cursor_down", "Cursor Down", show=False),
        Binding("pageup", "cursor_page_up", "Page Up", show=False),
        Binding("pagedown", "cursor_page_down", "Page Down", show=False),
        Binding("home", "cursor_home", "Go to Top", show=False),
        Binding("end", "cursor_end", "Go to Bottom", show=False),
    ]

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.viewport_content: Optional[ViewportContent] = None
        self.app_ref = None

    def set_viewport_content(self, viewport_content: ViewportContent):
        self.viewport_content = viewport_content
        self._update_display()

    def update_viewport_height(self, new_height: int):
        if self.viewport_content:
            self.viewport_content.update_viewport_height(new_height)
            self._update_display()

    def _update_display(self):
        if not self.viewport_content:
            self.update("Select a chapter to begin reading...")
            return
        viewport_lines = self.viewport_content.get_current_viewport_lines()
        if not viewport_lines:
            self.update("No content available...")
            return
        content_text = Text()
        current_global_cursor = self.viewport_content.get_cursor_global_position()
        for idx, line in enumerate(viewport_lines):
            line_str = line.rstrip()
            if idx < len(viewport_lines) - 1:
                line_str += "\n"
            global_line_idx = (
                self.viewport_content.current_page
                * self.viewport_content.viewport_height
                + idx
            )
            should_highlight = False
            cursor_logical_idx = self.viewport_content.line_to_logical.get(
                current_global_cursor
            )
            if cursor_logical_idx is not None:
                cursor_logical = self.viewport_content.logical_lines[cursor_logical_idx]
                if (
                    cursor_logical["is_paragraph"]
                    and cursor_logical["start_line"]
                    <= global_line_idx
                    <= cursor_logical["end_line"]
                ):
                    should_highlight = True
            if not should_highlight and idx == self.viewport_content.cursor_in_page:
                should_highlight = True
            if should_highlight:
                content_text.append(line_str, style="reverse")
            else:
                content_text.append(line_str)
        self.update(content_text)

    def action_cursor_up(self):
        if self.app_ref:
            self.app_ref.action_content_up()

    def action_cursor_down(self):
        if self.app_ref:
            self.app_ref.action_content_down()

    def action_cursor_page_up(self):
        if self.app_ref:
            self.app_ref.action_content_page_up()

    def action_cursor_page_down(self):
        if self.app_ref:
            self.app_ref.action_content_page_down()

    def action_cursor_home(self):
        if self.app_ref:
            self.app_ref.action_content_home()

    def action_cursor_end(self):
        if self.app_ref:
            self.app_ref.action_content_end()
