from __future__ import annotations

import json
import re
from pathlib import Path
from typing import Dict, List

from g4f.client import Client

from .symbol_logic import (
    PUA_START,
    RootSpec,
    compose_root,
    enumerate_symbol_space,
    symbol_id,
)


def _extract_json(text: str) -> List[Dict[str, str]]:
    text = text.strip()
    try:
        data = json.loads(text)
        if isinstance(data, list):
            return data
    except json.JSONDecodeError:
        pass
    match = re.search(r"\[[\s\S]*\]", text)
    if not match:
        raise ValueError("No JSON list found in model response.")
    data = json.loads(match.group(0))
    if not isinstance(data, list):
        raise ValueError("JSON payload is not a list.")
    return data


def _fallback_entries(count: int) -> List[Dict[str, str]]:
    glosses = [
        "person",
        "house",
        "water",
        "fire",
        "earth",
        "sky",
        "day",
        "night",
        "eat",
        "drink",
        "go",
        "come",
        "see",
        "know",
        "make",
        "give",
        "big",
        "small",
        "good",
        "bad",
        "inside",
        "outside",
        "near",
        "far",
        "mind",
        "time",
        "name",
        "group",
        "path",
        "tool",
    ]
    out: List[Dict[str, str]] = []
    i = 0
    while len(out) < count:
        out.append({"gloss": glosses[i % len(glosses)] + f"_{i}", "semantic_class": ""})
        i += 1
    return out


def _semantic_guess(gloss: str) -> str:
    g = gloss.lower()
    action_hints = ("eat", "drink", "go", "come", "see", "know", "make", "give", "do", "build")
    quality_hints = ("big", "small", "good", "bad", "hot", "cold", "new", "old")
    relation_hints = ("inside", "outside", "near", "far", "with", "from", "to", "between")
    abstract_hints = ("mind", "time", "name", "idea", "rule", "number")
    if any(k in g for k in action_hints):
        return "action"
    if any(k in g for k in quality_hints):
        return "quality"
    if any(k in g for k in relation_hints):
        return "relation"
    if any(k in g for k in abstract_hints):
        return "abstract"
    return "entity"


def generate_dictionary(count: int = 96, model: str = "gpt-5-mini") -> List[RootSpec]:
    prompt = (
        f"Create {count} base roots for a tiny agglutinative language.\n"
        "Return ONLY JSON list. Each item: "
        '{"gloss":"english concept", "semantic_class":"entity|action|quality|relation|abstract"}.\n'
        "Concepts must be common and concrete when possible."
    )

    entries: List[Dict[str, str]]
    try:
        client = Client()
        response = client.chat.completions.create(
            model=model,
            messages=[{"role": "user", "content": prompt}],
            web_search=False,
        )
        content = response.choices[0].message.content
        entries = _extract_json(content)
    except Exception:
        entries = _fallback_entries(count)

    slots = enumerate_symbol_space(max(count, len(entries)))
    out: List[RootSpec] = []

    for idx, raw in enumerate(entries[:count]):
        gloss = str(raw.get("gloss", f"concept_{idx}")).strip().lower().replace(" ", "_")
        sc = str(raw.get("semantic_class", "")).strip().lower()
        if sc not in {"entity", "action", "quality", "relation", "abstract"}:
            sc = _semantic_guess(gloss)

        _, onset, vowel, coda = slots[idx]
        root = compose_root(onset, vowel, coda)
        out.append(
            RootSpec(
                root=root,
                gloss=gloss,
                semantic_class=sc,
                onset=onset,
                vowel=vowel,
                coda=coda,
                symbol_id=symbol_id(sc, onset, vowel, coda),
                codepoint=PUA_START + idx,
            )
        )
    return out


def save_dictionary(specs: List[RootSpec], out_path: Path) -> None:
    out_path.parent.mkdir(parents=True, exist_ok=True)
    payload = [
        {
            "root": s.root,
            "gloss": s.gloss,
            "semantic_class": s.semantic_class,
            "onset": s.onset,
            "vowel": s.vowel,
            "coda": s.coda,
            "symbol_id": s.symbol_id,
            "symbol": s.symbol,
            "codepoint": f"U+{s.codepoint:04X}",
            "char": s.char,
        }
        for s in specs
    ]
    out_path.write_text(json.dumps(payload, indent=2, ensure_ascii=False), encoding="utf-8")


def load_dictionary(path: Path) -> List[RootSpec]:
    data = json.loads(path.read_text(encoding="utf-8"))
    out: List[RootSpec] = []
    for item in data:
        cp_str = str(item["codepoint"]).replace("U+", "")
        cp = int(cp_str, 16)
        out.append(
            RootSpec(
                root=item["root"],
                gloss=item["gloss"],
                semantic_class=item["semantic_class"],
                onset=item["onset"],
                vowel=item["vowel"],
                coda=item["coda"],
                symbol_id=item["symbol_id"],
                codepoint=cp,
            )
        )
    return out


def build_lookup(specs: List[RootSpec]) -> Dict[str, RootSpec]:
    return {s.gloss: s for s in specs}


def build_reverse_lookup(specs: List[RootSpec]) -> Dict[str, RootSpec]:
    out: Dict[str, RootSpec] = {}
    for s in specs:
        out[s.root] = s
        out[s.symbol] = s
    return out
