#!/usr/bin/env python3
"""Browse the OnixOS Special Easter Egg documents in a curses UI."""

from __future__ import annotations

import curses
import os
import re
import textwrap
import webbrowser
from pathlib import Path
from urllib.parse import unquote, urlparse


DOCUMENT_ROOT = Path(__file__).resolve().parent / "eggs"
DOCUMENT_EXTENSIONS = {".md", ".markdown", ".wiki", ".txt"}
MARKDOWN_LINK = re.compile(r"!?\[[^\]]*\]\(([^)\s]+)(?:\s+['\"][^)]*['\"])?\)")
WEB_LINK = re.compile(r"https?://[^\s<>)]+")

COLOR_HEADER = 1
COLOR_SELECTED = 2
COLOR_ACCENT = 3
COLOR_FOOTER = 4
COLOR_MUTED = 5


def setup_colors() -> None:
    if not curses.has_colors():
        return
    curses.start_color()
    try:
        curses.use_default_colors()
    except curses.error:
        pass
    curses.init_pair(COLOR_HEADER, curses.COLOR_CYAN, -1)
    curses.init_pair(COLOR_SELECTED, curses.COLOR_BLACK, curses.COLOR_CYAN)
    curses.init_pair(COLOR_ACCENT, curses.COLOR_MAGENTA, -1)
    curses.init_pair(COLOR_FOOTER, curses.COLOR_BLACK, curses.COLOR_YELLOW)
    curses.init_pair(COLOR_MUTED, curses.COLOR_BLUE, -1)


def style(pair: int, attributes: int = 0) -> int:
    return curses.color_pair(pair) | attributes if curses.has_colors() else attributes


def put(screen: curses.window, row: int, column: int, text: str,
        width: int, attributes: int = 0) -> None:
    if width > 0:
        screen.addnstr(row, column, text, width, attributes)


def documents() -> list[Path]:
    if not DOCUMENT_ROOT.is_dir():
        return []
    return sorted(
        (
            path
            for path in DOCUMENT_ROOT.rglob("*")
            if path.is_file() and path.suffix.lower() in DOCUMENT_EXTENSIONS
        ),
        key=lambda path: path.relative_to(DOCUMENT_ROOT).as_posix().lower(),
    )


def read_document(path: Path) -> list[str]:
    try:
        content = path.read_text(encoding="utf-8")
    except UnicodeDecodeError:
        content = path.read_text(encoding="utf-8", errors="replace")
    return content.splitlines() or ["(Bu belge boş.)"]


def document_links(path: Path) -> list[tuple[str, str]]:
    """Return unique links with display text and browser-ready targets."""
    links: list[tuple[str, str]] = []
    seen: set[str] = set()
    for line in read_document(path):
        for match in MARKDOWN_LINK.finditer(line):
            target = match.group(1).strip().strip("<>")
            if not target:
                continue
            label = "Görsel" if match.group(0).startswith("!") else "Bağlantı"
            browser_target = browser_url(path, target)
            if browser_target not in seen:
                links.append((label, browser_target))
                seen.add(browser_target)
        for match in WEB_LINK.finditer(line):
            target = match.group(0).rstrip(".,;:!?\"'")
            if target not in seen:
                links.append(("Web", target))
                seen.add(target)
    return links


def browser_url(document: Path, target: str) -> str:
    """Convert relative document/image links to file URLs."""
    parsed = urlparse(target)
    if parsed.scheme or target.startswith("//"):
        return target
    path_part, separator, fragment = target.partition("#")
    local_path = (document.parent / unquote(path_part)).resolve()
    result = local_path.as_uri()
    return f"{result}#{fragment}" if separator else result


def choose_link(screen: curses.window, links: list[tuple[str, str]]) -> str | None:
    height, width = screen.getmaxyx()
    screen.erase()
    screen.addnstr(0, 0, " Açılacak bağlantıyı seçin (Esc: iptal) ", max(1, width - 1), curses.A_REVERSE)
    visible = max(1, height - 3)
    for index, (label, target) in enumerate(links[:visible], start=1):
        screen.addnstr(index, 0, f"{index:>2}. [{label}] {target}", max(1, width - 1))
    screen.addnstr(height - 2, 0, "Numara: ", max(1, width - 1))
    screen.refresh()
    curses.echo()
    try:
        value = screen.getstr(height - 2, min(8, max(0, width - 1)), 8).decode(errors="ignore")
    finally:
        curses.noecho()
    if not value.strip() or value.strip() == "0":
        return None
    try:
        selected = int(value) - 1
    except ValueError:
        return None
    return links[selected][1] if 0 <= selected < len(links) else None


def wrapped_lines(lines: list[str], width: int) -> list[str]:
    result: list[str] = []
    for line in lines:
        if not line:
            result.append("")
            continue
        result.extend(textwrap.wrap(line, width=max(width, 1),
                                    replace_whitespace=False,
                                    drop_whitespace=False) or [""])
    return result


def draw_message(screen: curses.window, message: str) -> None:
    screen.erase()
    height, width = screen.getmaxyx()
    screen.addnstr(max(0, height // 2), 2, message, max(1, width - 4))
    screen.addnstr(max(0, height - 1), 0, "Çıkmak için q veya Esc", max(1, width - 1))
    screen.refresh()


def select_document(screen: curses.window, files: list[Path]) -> int | None:
    """Let the user choose a document before opening the reader."""
    selected = 0
    while True:
        height, width = screen.getmaxyx()
        screen.erase()
        put(screen, 0, 0, "  ONIXOS  //  EASTER EGGS", width,
            style(COLOR_HEADER, curses.A_BOLD))
        put(screen, 1, 0, "  Gizli köşeler, eski hikâyeler ve unutulmaması gerekenler", width,
            style(COLOR_MUTED))
        put(screen, 2, 0, "  " + "─" * max(0, width - 4), width,
            style(COLOR_ACCENT))
        visible = max(1, height - 6)
        first = min(selected, max(0, len(files) - visible))
        for row, index in enumerate(range(first, min(first + visible, len(files))), start=1):
            relative = files[index].relative_to(DOCUMENT_ROOT).as_posix()
            marker = ">> " if index == selected else "   "
            attribute = style(COLOR_SELECTED, curses.A_BOLD) if index == selected else 0
            put(screen, row + 2, 0, f" {marker}{relative}", width, attribute)
        put(screen, height - 3, 0, "  " + "─" * max(0, width - 4), width,
            style(COLOR_ACCENT))
        put(screen, height - 2, 0, "  ↑↓ seç   ENTER oku   q çıkış", width,
            style(COLOR_FOOTER, curses.A_BOLD))
        put(screen, height - 1, 0, f"  {selected + 1}/{len(files)} belge", width,
            style(COLOR_MUTED))
        screen.refresh()

        key = screen.getch()
        if key in (ord("q"), ord("Q"), 27):
            return None
        if key in (curses.KEY_DOWN, ord("j")):
            selected = min(selected + 1, len(files) - 1)
        elif key in (curses.KEY_UP, ord("k")):
            selected = max(0, selected - 1)
        elif key in (curses.KEY_ENTER, 10, 13):
            return selected


def browse(screen: curses.window) -> None:
    curses.curs_set(0)
    screen.keypad(True)
    setup_colors()
    files = documents()
    if not files:
        draw_message(screen, "Henüz görüntülenecek bir Special Egg belgesi yok.")
        while screen.getch() not in (ord("q"), ord("Q"), 27):
            pass
        return

    while True:
        document_index = select_document(screen, files)
        if document_index is None:
            return
        offset = 0
        while True:
            height, width = screen.getmaxyx()
            relative = files[document_index].relative_to(DOCUMENT_ROOT).as_posix()
            title = f"  ONIXOS  //  EASTER EGGS   [{document_index + 1}/{len(files)}]"
            footer = "  ↑↓ kaydır   o link aç   n sonraki   b liste   q çıkış"
            body_height = max(1, height - 4)
            content = wrapped_lines(read_document(files[document_index]), max(1, width - 2))
            offset = min(offset, max(0, len(content) - body_height))

            screen.erase()
            put(screen, 0, 0, title, width, style(COLOR_HEADER, curses.A_BOLD))
            put(screen, 1, 0, f"  {relative}", width, style(COLOR_MUTED))
            for row, line in enumerate(content[offset:offset + body_height], start=1):
                put(screen, row + 2, 0, f"  {line}", width - 1)
            put(screen, height - 1, 0, footer, width, style(COLOR_FOOTER, curses.A_BOLD))
            progress = f"  {offset + 1}-{min(offset + body_height, len(content))}/{len(content)} satır"
            put(screen, height - 2, 0, progress, width, style(COLOR_MUTED))
            screen.refresh()

            key = screen.getch()
            if key in (ord("q"), ord("Q"), 27):
                return
            if key in (ord("b"), ord("B")):
                break
            if key in (curses.KEY_DOWN, ord("j")):
                offset = min(offset + 1, max(0, len(content) - body_height))
            elif key in (curses.KEY_UP, ord("k")):
                offset = max(0, offset - 1)
            elif key in (curses.KEY_NPAGE, ord(" ")):
                offset = min(offset + body_height, max(0, len(content) - body_height))
            elif key in (curses.KEY_PPAGE, ord("p")):
                offset = max(0, offset - body_height)
            elif key in (ord("o"), ord("O")):
                links = document_links(files[document_index])
                if links:
                    target = choose_link(screen, links)
                    if target:
                        webbrowser.open(target)
                offset = 0
            elif key in (ord("n"), ord("N")) and document_index < len(files) - 1:
                document_index += 1
                offset = 0


def main() -> None:
    os.environ.setdefault("ESCDELAY", "25")
    curses.wrapper(browse)


if __name__ == "__main__":
    main()
