"""Manage the single global Adom symbol library (Adom.kicad_sym).

All symbols installed by Adom Desktop live in one KiCad symbol
library file.  This module handles:

- Creating the library file if it doesn't exist
- Registering it in sym-lib-table if not already there
- Adding/updating individual symbols inside the library
- Listing symbols currently in the library
"""

import re
from pathlib import Path

from lib_table import LibTable, LibEntry

# The one library name used everywhere
ADOM_LIB_NAME = "Adom"
ADOM_LIB_FILE = "Adom.kicad_sym"
ADOM_LIB_DESCRIPTION = "Adom Desktop - custom symbols"

# Regex to extract top-level (symbol "NAME" ...) blocks.
# Top-level symbols are indented with exactly one leading whitespace group
# (2 spaces typically) whereas sub-unit symbols have deeper indentation.
# We use a balanced-paren counter to find the closing paren.
_SYMBOL_NAME_RE = re.compile(r'^\s+\(symbol\s+"([^"]+)"', re.MULTILINE)

# Template for an empty Adom.kicad_sym
_EMPTY_LIB = (
    "(kicad_symbol_lib\n"
    '  (version 20231120)\n'
    '  (generator "adom-desktop")\n'
    ")\n"
)


def _get_paths(kicad_info: dict) -> tuple[Path, Path] | None:
    """Return (sym_lib_table_path, adom_kicad_sym_path) or None."""
    config_dir = kicad_info.get("config_dir")
    user_dir = kicad_info.get("user_dir")
    if not config_dir or not user_dir:
        return None
    table_path = Path(config_dir) / "sym-lib-table"
    sym_dir = Path(user_dir) / "symbols"
    sym_dir.mkdir(parents=True, exist_ok=True)
    lib_path = sym_dir / ADOM_LIB_FILE
    return table_path, lib_path


def ensure_adom_library(kicad_info: dict) -> dict:
    """Make sure Adom.kicad_sym exists and is registered.

    Returns:
        {
            "exists": bool,        # whether the file existed before this call
            "registered": bool,    # whether it was already in sym-lib-table
            "created": bool,       # whether we had to create the file
            "symbols": [str],      # symbol names currently in the library
        }
    """
    if not kicad_info.get("installed"):
        return {"exists": False, "registered": False, "created": False, "symbols": [],
                "error": "KiCad not installed"}

    paths = _get_paths(kicad_info)
    if not paths:
        return {"exists": False, "registered": False, "created": False, "symbols": [],
                "error": "KiCad config/user directory not found"}

    table_path, lib_path = paths

    # --- Ensure the .kicad_sym file exists ---
    existed = lib_path.exists()
    if not existed:
        lib_path.write_text(_EMPTY_LIB, encoding="utf-8")

    # --- Ensure it's registered in sym-lib-table ---
    table = LibTable.parse_file(str(table_path))
    was_registered = table.has_library(ADOM_LIB_NAME)

    if not was_registered:
        uri = str(lib_path).replace("\\", "/")
        entry = LibEntry(
            name=ADOM_LIB_NAME,
            lib_type="KiCad",
            uri=uri,
            options="",
            descr=ADOM_LIB_DESCRIPTION,
        )
        table.add_library(entry)
        table.write_file(str(table_path))

    # --- List current symbols ---
    symbols = list_symbols(lib_path)

    return {
        "exists": existed,
        "registered": was_registered,
        "created": not existed,
        "symbols": symbols,
    }


def list_symbols(lib_path: Path) -> list[str]:
    """Return the names of all top-level symbols in a .kicad_sym file."""
    if not lib_path.exists():
        return []
    text = lib_path.read_text(encoding="utf-8")
    # Top-level symbols: (symbol "NAME" at indentation level 1 (2 spaces)
    # Sub-units like "NAME_0_0" are children — we skip those.
    names = []
    for m in _SYMBOL_NAME_RE.finditer(text):
        name = m.group(1)
        # Skip sub-unit symbols (contain _N_N suffix pattern)
        if not re.search(r"_\d+_\d+$", name):
            names.append(name)
    return names


def _extract_symbol_block(text: str, start_pos: int) -> str:
    """Extract a complete (symbol ...) block starting from start_pos.

    Uses balanced parenthesis counting to find the matching close paren.
    """
    depth = 0
    i = start_pos
    while i < len(text):
        if text[i] == "(":
            depth += 1
        elif text[i] == ")":
            depth -= 1
            if depth == 0:
                return text[start_pos : i + 1]
        i += 1
    # Shouldn't happen with valid files, but return what we have
    return text[start_pos:]


def _extract_top_level_symbols(text: str) -> list[tuple[str, str]]:
    """Extract all top-level (symbol "NAME" ...) blocks from a .kicad_sym.

    Returns list of (name, full_block_text) tuples.
    Each block includes the symbol and all its sub-units.
    """
    results = []
    # Find all top-level symbol openings (indented with exactly 2 spaces)
    for m in re.finditer(r'^  \(symbol "([^"]+)"', text, re.MULTILINE):
        name = m.group(1)
        # Skip sub-unit symbols
        if re.search(r"_\d+_\d+$", name):
            continue
        block = _extract_symbol_block(text, m.start())
        results.append((name, block))
    return results


def add_symbol(kicad_info: dict, symbol_text: str) -> dict:
    """Add or update a symbol in Adom.kicad_sym.

    Args:
        kicad_info: KiCad detection info dict.
        symbol_text: Full content of a .kicad_sym file containing the symbol(s).

    Returns:
        {
            "success": bool,
            "added": [str],     # names of symbols added/updated
            "lib_path": str,    # path to Adom.kicad_sym
        }
    """
    # Ensure library exists first
    ensure_result = ensure_adom_library(kicad_info)
    if ensure_result.get("error"):
        return {"success": False, "error": ensure_result["error"]}

    paths = _get_paths(kicad_info)
    if not paths:
        return {"success": False, "error": "KiCad paths not found"}

    _, lib_path = paths

    # Extract symbols from incoming text
    incoming_symbols = _extract_top_level_symbols(symbol_text)
    if not incoming_symbols:
        return {"success": False, "error": "No symbols found in the provided file content"}

    # Read the existing library
    existing_text = lib_path.read_text(encoding="utf-8")

    # Extract existing symbols
    existing_symbols = _extract_top_level_symbols(existing_text)
    existing_by_name = {name: block for name, block in existing_symbols}

    # Merge: update existing, add new
    added_names = []
    for name, block in incoming_symbols:
        existing_by_name[name] = block
        added_names.append(name)

    # Rebuild the library file
    # Parse header (version + generator lines)
    header_match = re.match(
        r'(\(kicad_symbol_lib\s*\n(?:\s+\(version[^\)]+\)\s*\n)?(?:\s+\(generator[^\)]+\)\s*\n)?)',
        existing_text,
    )
    if header_match:
        header = header_match.group(1)
    else:
        header = "(kicad_symbol_lib\n  (version 20231120)\n  (generator \"adom-desktop\")\n"

    # Build new file content
    parts = [header]
    for _name, block in existing_by_name.items():
        parts.append(f"  {block.strip()}\n")
    parts.append(")\n")

    new_text = "".join(parts)
    lib_path.write_text(new_text, encoding="utf-8")

    return {
        "success": True,
        "added": added_names,
        "lib_path": str(lib_path),
    }