"""
Nexa Language — nexa explain command (Phase 7 / v27)
Context-sensitive help for any symbol, keyword, or error code.

Usage:
    nexa explain "result.ok"         # explains a stdlib symbol
    nexa explain "trait"             # shows keyword docs + examples
    nexa explain error E0023         # explains specific error code
    nexa explain "expect"            # explains testing matchers
"""
from __future__ import annotations
import textwrap

# ── Knowledge base ─────────────────────────────────────────────────────────────
EXPLAIN_DB: dict = {
    # Keywords
    "let": {
        "kind": "keyword",
        "summary": "Declare an immutable binding.",
        "example": 'let name: String = "Alice"\nlet x = 42    // type inferred',
        "notes": "Cannot be reassigned. Use `var` for mutable variables.",
    },
    "var": {
        "kind": "keyword",
        "summary": "Declare a mutable binding.",
        "example": "var count: Int = 0\ncount += 1",
        "notes": "Prefer `let` unless you need mutation.",
    },
    "const": {
        "kind": "keyword",
        "summary": "Module-level immutable constant — enforced at compile and runtime.",
        "example": "const MAX_RETRIES: Int = 3\nconst APP_NAME: String = \"Nexa\"",
        "notes": "Constants are globally visible and cannot be reassigned anywhere.",
    },
    "fn": {
        "kind": "keyword",
        "summary": "Lambda / anonymous function expression.",
        "example": "let double = fn(x) => x * 2\nlet add = fn(a, b) => a + b",
        "notes": "Use `def` for named functions. `fn` is for inline lambdas.",
    },
    "def": {
        "kind": "keyword",
        "summary": "Define a named function.",
        "example": "def greet(name: String) -> String {\n    return f\"Hello, {name}!\"\n}",
    },
    "trait": {
        "kind": "keyword",
        "summary": "Define a trait (interface) — a contract of required methods.",
        "example": "trait Printable {\n    def print(self) -> String {}\n}\nimpl Printable for MyClass {}",
        "notes": "Use `impl Trait for Class` to implement. Traits support default method bodies.",
    },
    "impl": {
        "kind": "keyword",
        "summary": "Implement a trait for a class.",
        "example": "impl Serializable for User {\n    def to_json(self) -> String {\n        return f'{{\"name\": \"{self.name}\"}}'\n    }\n}",
    },
    "match": {
        "kind": "keyword",
        "summary": "Pattern matching — exhaustive switch with destructuring.",
        "example": 'match response {\n    case Ok(data) if data.len > 0 => process(data)\n    case Ok(_)                    => print("Empty")\n    case Err(msg)                 => throw msg\n}',
    },
    "assert": {
        "kind": "keyword",
        "summary": "Native assertion — raises NexaAssertError if condition is false.",
        "example": 'assert x > 0, "x must be positive"\nassert model.accuracy > 0.9',
    },
    "async": {
        "kind": "keyword",
        "summary": "Define an async function or async generator.",
        "example": "async def fetch(url: String) {\n    return await http.get(url)\n}\n\nasync def stream() {\n    yield 1; yield 2\n}",
    },
    "spawn": {
        "kind": "builtin",
        "summary": "Run tasks concurrently and collect all results.",
        "example": 'let results = spawn({\n    "a": fn() => fetch("https://api.example.com/a"),\n    "b": fn() => fetch("https://api.example.com/b"),\n})\nprint(results["a"])',
        "notes": "Returns a dict of name → result. All tasks run concurrently in threads.",
    },
    "model": {
        "kind": "keyword",
        "summary": "Declare a neural network model (AI-native syntax).",
        "example": "model Classifier {\n    layers: [Dense(256), ReLU, Dropout(0.5), Dense(10)]\n    optimizer: \"adam\"\n    loss: \"sparse_categorical_crossentropy\"\n}",
    },
    "train": {
        "kind": "keyword",
        "summary": "Train a model on a dataset.",
        "example": "train Classifier on my_dataset { epochs: 50, batch: 32 }",
    },
    "predict": {
        "kind": "keyword",
        "summary": "Run inference on a trained model.",
        "example": "let label = predict Classifier(input_data)",
    },
    "newtype": {
        "kind": "keyword",
        "summary": "Create a distinct branded type wrapping a primitive.",
        "example": "newtype Celsius = Float\nnewtype Fahrenheit = Float\n\nlet temp: Celsius = Celsius(100.0)\nlet f: Fahrenheit = temp   // ❌ type error",
        "notes": "Newtypes are not interchangeable even if they wrap the same base type.",
    },
    # stdlib symbols
    "result.ok": {
        "kind": "stdlib",
        "summary": "Wrap a value in a successful Result.",
        "example": 'import "result"\nlet r = result.ok(42)\nprint(result.unwrap(r))   // → 42',
    },
    "result.err": {
        "kind": "stdlib",
        "summary": "Wrap an error value in a failed Result.",
        "example": 'import "result"\nlet e = result.err("not found")\nprint(result.is_err(e))   // → true',
    },
    "option.some": {
        "kind": "stdlib",
        "summary": "Wrap a present value in an Option.",
        "example": 'import "option"\nlet o = option.some("hello")\nprint(option.unwrap(o))   // → "hello"',
    },
    "option.none": {
        "kind": "stdlib",
        "summary": "Create an absent Option value.",
        "example": 'import "option"\nlet n = option.none()\nprint(option.is_none(n))  // → true',
    },
    "expect": {
        "kind": "builtin",
        "summary": "Rich test assertion — fluent matcher API.",
        "example": (
            "expect(x).to_equal(5)\n"
            "expect(arr).to_contain(3)\n"
            "expect(response.status).to_be_in_range(200, 299)\n"
            "expect(model.accuracy).to_be_greater_than(0.9)\n"
            "expect(fn).to_throw(\"NullError\")"
        ),
        "notes": "All matchers support .not_ prefix for negation: expect(x).not_.to_equal(5)",
    },
    # Error codes
    "E0001": {
        "kind": "error",
        "summary": "Unexpected token in source.",
        "example": "def greet(name {\n           ^^^^^ expected ')' before '{'",
        "fix": "Check for missing closing parentheses, brackets, or braces.",
    },
    "E0010": {
        "kind": "error",
        "summary": "Null assignment to non-nullable type.",
        "example": "let x: Int = null   // x is Int, not Int?",
        "fix": "Change type to 'Int?' if null is valid, or assign a non-null value.",
    },
    "E0011": {
        "kind": "error",
        "summary": "Assignment to immutable const binding.",
        "example": "const MAX = 10\nMAX = 20   // ❌ const binding",
        "fix": "Use 'var' instead of 'const' if you need to reassign.",
    },
    "E0020": {
        "kind": "error",
        "summary": "Generic type violation at runtime.",
        "example": "let nums: List<Int> = [1, 2, \"oops\"]\n                                 ^^^^^^ expected Int, got String",
        "fix": "Ensure all list elements match the declared generic type.",
    },
}


def explain(query: str) -> str:
    """Look up documentation for a symbol, keyword, or error code."""
    key = query.strip().lower().lstrip("error").strip()
    # Try exact match
    entry = EXPLAIN_DB.get(query.strip()) or EXPLAIN_DB.get(key)

    if not entry:
        # Fuzzy: find all keys that contain the query
        matches = [k for k in EXPLAIN_DB if query.lower() in k.lower()]
        if not matches:
            return (f"  ❓ No documentation found for '{query}'\n"
                    f"  Try: nexa explain \"let\" | \"trait\" | \"result.ok\" | \"E0001\"")
        if len(matches) == 1:
            entry = EXPLAIN_DB[matches[0]]
            query = matches[0]
        else:
            lines = [f"  Found {len(matches)} matches for '{query}':"]
            for m in matches:
                lines.append(f"    • nexa explain \"{m}\"")
            return "\n".join(lines)

    kind  = entry.get("kind", "symbol")
    icons = {"keyword": "🔑", "builtin": "⚡", "stdlib": "📦", "error": "❌"}
    icon  = icons.get(kind, "📖")

    lines = [
        f"\n  {icon} [{kind.upper()}] {query}",
        f"  {'─' * 50}",
        f"  {entry['summary']}",
    ]
    if "example" in entry:
        lines.append(f"\n  Example:")
        for ln in entry["example"].splitlines():
            lines.append(f"    {ln}")
    if "notes" in entry:
        lines.append(f"\n  Note: {entry['notes']}")
    if "fix" in entry:
        lines.append(f"\n  How to fix: {entry['fix']}")
    lines.append("")
    return "\n".join(lines)


def run_explain(args: list):
    """Entry point for `nexa explain` CLI command."""
    if not args:
        print("  Usage: nexa explain <symbol|keyword|error>")
        print("  Examples:")
        print('    nexa explain "let"')
        print('    nexa explain "result.ok"')
        print('    nexa explain "trait"')
        print('    nexa explain error E0010')
        print(f"\n  Available topics ({len(EXPLAIN_DB)}):")
        keywords = sorted(k for k, v in EXPLAIN_DB.items() if v["kind"] == "keyword")
        stdlib   = sorted(k for k, v in EXPLAIN_DB.items() if v["kind"] == "stdlib")
        builtins = sorted(k for k, v in EXPLAIN_DB.items() if v["kind"] == "builtin")
        errors   = sorted(k for k, v in EXPLAIN_DB.items() if v["kind"] == "error")
        print(f"    Keywords : {', '.join(keywords)}")
        print(f"    Stdlib   : {', '.join(stdlib)}")
        print(f"    Builtins : {', '.join(builtins)}")
        print(f"    Errors   : {', '.join(errors)}")
        return
    query = " ".join(args).strip('"').strip("'")
    print(explain(query))
