#!/usr/bin/env python3
"""
BloomOS Dispatcher

One front-door string → HelixBackplane.run_task(...)

Supported forms (examples):
  - "ls -a"
  - "list.all.files"
  - "sys.shell.ls_a"
  - "print working directory"
  - "fix this python function"
"""

from __future__ import annotations

import os
from dataclasses import dataclass
from typing import Dict, Any, List, Optional, Tuple

from .backplane import DEFAULT_BACKPLANE
from .programs import BASE_PROGRAM
from .program_builder import build_program
from .sym import sym_capsule, capsule_to_json, lower_task_to_sym, capsule_id


def _is_kernel_mode() -> bool:
    """Check if BloomOS is running in kernel mode."""
    return os.getenv("BLOOMOS_MODE", "direct") == "kernel"


@dataclass
class CommandIntent:
    task_type: str          # "sysop", "code", "math", "json", "search", etc.
    description: str        # human/CLI description
    source: str             # "bash", "dot", "sys.shell", "natural", "fallback"
    canonical: Optional[str] = None  # canonical dot-notation, if any


# ---- Cheat sheet mappings ----------------------------------------------------

# Core sys.shell mappings you gave:
#   sys.shell.pwd      = print.working.directory
#   sys.shell.ls_a     = list.all.files
#   sys.shell.ps_aux   = processes.list
#   sys.shell.kill     = kill.process

DOT_TABLE: Dict[str, Tuple[str, str]] = {
    # dot_notation           -> (task_type, description)
    "print.working.directory": ("sysop", "pwd"),
    "list.all.files":         ("sysop", "ls -a"),
    "processes.list":         ("sysop", "ps aux"),
    "kill.process":           ("sysop", "kill"),  # PID handled by higher layer/CLI
}

BASH_TABLE: Dict[str, Tuple[str, str]] = {
    # bash                   -> (task_type, canonical_dot)
    "pwd":    ("sysop", "print.working.directory"),
    "ls -a":  ("sysop", "list.all.files"),
    "ps aux": ("sysop", "processes.list"),
    "ls":     ("sysop", "list.files"),  # softer / generic
    # kill is special (may have PID) – handled separately
}

SYS_SHELL_TABLE: Dict[str, Tuple[str, str]] = {
    # sys.shell.*            -> (task_type, canonical_dot)
    "sys.shell.pwd":    ("sysop", "print.working.directory"),
    "sys.shell.ls_a":   ("sysop", "list.all.files"),
    "sys.shell.ps_aux": ("sysop", "processes.list"),
    "sys.shell.kill":   ("sysop", "kill.process"),
}


# ---- Intent parsing ----------------------------------------------------------


def _normalize(s: str) -> str:
    return " ".join(s.strip().split())


def _classify_natural(text: str) -> str:
    """Very simple heuristic task classifier for natural language."""
    t = text.lower()

    # Strong math hints
    math_tokens = ["integral", "derivative", "limit", "matrix", "probability",
                   "theorem", "equation", "solve for", "∫", "+", "-", "×", "÷"]
    if any(tok in t for tok in math_tokens):
        return "math"

    # Strong code hints
    code_tokens = ["python", "function", "class", "javascript", "code",
                   "write a script", "refactor", "bug", "compile"]
    if any(tok in t for tok in code_tokens):
        return "code"

    # JSON / schema hints
    json_tokens = ["json", "schema", "fields", "keys", "api response", "object"]
    if any(tok in t for tok in json_tokens):
        return "json"

    # Search / retrieval hints
    search_tokens = ["search", "find", "lookup", "look up", "google", "query"]
    if any(tok in t for tok in search_tokens):
        return "search"

    # Sysop-ish hints
    sysop_tokens = ["list files", "current directory", "processes", "kill process",
                    "cpu", "memory usage", "disk space"]
    if any(tok in t for tok in sysop_tokens):
        return "sysop"

    # Default: treat as code-style reasoning (balanced gate will handle)
    return "code"


def parse_command(raw: str) -> CommandIntent:
    """
    Parse a single front-door string into a CommandIntent.
    """
    if not raw or not raw.strip():
        raise ValueError("Empty command")

    original = raw
    raw = raw.strip()

    # 1) sys.shell.* form
    if raw.startswith("sys.shell."):
        # exact match
        if raw in SYS_SHELL_TABLE:
            task_type, dot = SYS_SHELL_TABLE[raw]
            _, description = DOT_TABLE.get(dot, (task_type, raw))
            return CommandIntent(
                task_type=task_type,
                description=description,
                source="sys.shell",
                canonical=dot,
            )

    # 2) dot-notation form: e.g. "list.all.files"
    if "." in raw and " " not in raw:
        dot = raw.lower()
        if dot in DOT_TABLE:
            task_type, description = DOT_TABLE[dot]
            return CommandIntent(
                task_type=task_type,
                description=description,
                source="dot",
                canonical=dot,
            )

    # 3) pure bash form: "ls -a", "pwd", "ps aux", etc.
    norm = _normalize(raw)
    if norm in BASH_TABLE:
        task_type, dot = BASH_TABLE[norm]
        # back-resolve bash description from DOT_TABLE if present
        _, description = DOT_TABLE.get(dot, (task_type, norm))
        return CommandIntent(
            task_type=task_type,
            description=description,
            source="bash",
            canonical=dot,
        )

    # Special-case kill with PID: "kill 1234"
    if norm.startswith("kill "):
        return CommandIntent(
            task_type="sysop",
            description=norm,   # let lower layer interpret PID
            source="bash",
            canonical="kill.process",
        )

    # 4) Natural sysop-ish phrasing: "list all files", etc.
    low = raw.lower()
    if "list all files" in low or "show all files" in low:
        return CommandIntent(
            task_type="sysop",
            description="ls -a",
            source="natural",
            canonical="list.all.files",
        )

    if "current directory" in low or "where am i" in low:
        return CommandIntent(
            task_type="sysop",
            description="pwd",
            source="natural",
            canonical="print.working.directory",
        )

    if "processes" in low or "running programs" in low:
        return CommandIntent(
            task_type="sysop",
            description="ps aux",
            source="natural",
            canonical="processes.list",
        )

    # 5) Fallback: classify natural language by intent (code/math/json/search)
    task_type = _classify_natural(raw)

    return CommandIntent(
        task_type=task_type,
        description=raw,   # full NL description goes through to backplane
        source="fallback",
        canonical=None,
    )


# ---- Dispatcher → Backplane --------------------------------------------------


def _map_intent_to_syscall(intent: CommandIntent) -> Optional[str]:
    """Map a CommandIntent to a kernel syscall name."""
    # Map task types to syscalls
    syscall_map = {
        "cdna_decode": "cdna.decode.v1",
        "cdna_encode": "cdna.encode.v1",
        "route_tensor": "routing.route_tensor.v1",
        "chunk_get": "chunkvault.get.v1",
        "chunk_store": "chunkvault.store.v1",
    }

    # Check description for specific keywords
    desc_lower = intent.description.lower()
    if "decode" in desc_lower and "cdna" in desc_lower:
        return "cdna.decode.v1"
    if "encode" in desc_lower and "cdna" in desc_lower:
        return "cdna.encode.v1"
    if "route" in desc_lower and "tensor" in desc_lower:
        return "routing.route_tensor.v1"
    if "chunk" in desc_lower and "get" in desc_lower:
        return "chunkvault.get.v1"

    return syscall_map.get(intent.task_type)


def dispatch_command(
    raw: str,
    *,
    anchors: Optional[List[str]] = None,
    gate_policy: Optional[str] = None,
    base_program: str = None,  # Uses BASE_PROGRAM from programs.py
    return_capsule: bool = False,  # Return full .sym capsule
    kernel_inputs: Optional[Dict[str, Any]] = None,  # Inputs for kernel syscall
) -> Dict[str, Any]:
    """
    High-level dispatcher:
      raw string → CommandIntent → DEFAULT_BACKPLANE.run_task(...)

    If return_capsule=True, wraps result in a .sym capsule with:
      - Soul anchor (cryptographic identity)
      - Reflex anchor (self-check)
      - 4D structure (data, context, time, meaning)

    Kernel Mode (BLOOMOS_MODE=kernel):
      If enabled, attempts to route through kernel syscalls first.
      The kernel_inputs parameter provides inputs for the syscall.
    """
    intent = parse_command(raw)

    # ---- KERNEL MODE: Try kernel syscall first ----
    if _is_kernel_mode():
        syscall_name = _map_intent_to_syscall(intent)
        if syscall_name and kernel_inputs:
            try:
                from .kernel import get_kernel

                kernel = get_kernel()
                result, receipt = kernel.registry.invoke(
                    syscall_name,
                    actor="dispatcher",
                    inputs=kernel_inputs,
                )

                # Return kernel result with receipt
                bp_result = {
                    "task_type": intent.task_type,
                    "description": intent.description,
                    "kernel_mode": True,
                    "kernel_syscall": syscall_name,
                    "kernel_result": result,
                    "kernel_receipt": receipt.to_dict(),
                }

                # Attach dispatcher provenance
                bp_result.setdefault("dispatcher", {})
                bp_result["dispatcher"].update({
                    "source": intent.source,
                    "canonical": intent.canonical,
                    "raw_input": raw,
                })

                return bp_result

            except Exception as e:
                # Fallback to backplane on kernel error
                print(f"[Dispatcher] Kernel error, falling back to backplane: {e}")

    # ---- BACKPLANE MODE: Default path ----
    # Build program from input if not explicitly provided
    # This is the KRISPER → Bio_Poetica → Helix8 compilation step
    compiled_program = base_program or build_program(raw)

    bp_result = DEFAULT_BACKPLANE.run_task(
        task_type=intent.task_type,
        description=intent.description,
        anchors=anchors,
        gate_policy=gate_policy,
        base_program=compiled_program,
    )

    # Attach a tiny bit of provenance for caller (no schema change)
    bp_result.setdefault("dispatcher", {})
    bp_result["dispatcher"].update(
        {
            "source": intent.source,
            "canonical": intent.canonical,
            "raw_input": raw,
        }
    )

    # Optionally wrap in .sym capsule for full BloomOS memory integration
    if return_capsule:
        capsule = lower_task_to_sym(
            task_type=intent.task_type,
            description=intent.description,
            program=compiled_program,
            result=bp_result,
        )
        bp_result["capsule"] = {
            "id": capsule_id(capsule),
            "soul_id": capsule.soul["soul_id"][:32] if capsule.soul else None,
            "reflex_count": len(capsule.reflex_anchors),
            "time_created": capsule.time_created,
            "json": capsule_to_json(capsule),
        }

    return bp_result


# ---- Simple CLI for quick local testing -------------------------------------


def _demo() -> None:
    import json
    import sys

    if len(sys.argv) < 2:
        print("Usage: python -m helix_cdc.bloomos.dispatcher '<command>'")
        print("Examples:")
        print("  python -m helix_cdc.bloomos.dispatcher 'list.all.files'")
        print("  python -m helix_cdc.bloomos.dispatcher 'ls -a'")
        print("  python -m helix_cdc.bloomos.dispatcher 'fix this python function'")
        sys.exit(1)

    raw = " ".join(sys.argv[1:])
    intent = parse_command(raw)
    print(f"[Dispatcher] Parsed intent: {intent}")

    try:
        result = dispatch_command(raw)
        print("\n[Backplane result]")
        print(json.dumps(result, indent=2, sort_keys=True))
    except Exception as e:
        print(f"[ERROR] {type(e).__name__}: {e}")
        sys.exit(1)


if __name__ == "__main__":
    _demo()
