import json
import re
import subprocess
import time
import uuid
from threading import RLock

from hiveos.config import AI_OLLAMA_MODEL, AI_OLLAMA_TIMEOUT_SEC, AI_PROVIDER, get_ai_route_model
from hiveos.intelligence.tracing import build_trace_event, emit_trace, now_iso
from hiveos.models.task import Task
from hiveos.runtime.states import AgentStatus, TaskStatus
from hiveos.runtime.task_runtime import hydrate_from_db_row as hydrate_task

SESSION_TERMINAL_TARGET_ID = "session-terminal"
SESSION_TERM_WORKER_PREFIX = "session-term-worker-"
SCOPED_TERM_WORKER_PREFIX = "scoped-term-worker-"
SESSION_TERM_WORKER_NAME = "SessionTermWorker"
SCOPED_TERM_WORKER_NAME = "ScopedTermWorker"


class IntentOrchestrationService:
    def __init__(self, core):
        self.core = core
        self._lock = RLock()
        self._intents = {}
        self._run_specs = {}
        self._outcomes = {}

    def stats(self):
        with self._lock:
            return {
                "intents_total": len(self._intents),
                "run_specs_total": len(self._run_specs),
                "outcomes_total": len(self._outcomes),
            }

    def compile_intent(self, prompt, user_id, session_id, scope="session", approval_mode="review_required", use_model=True):
        prompt_text = str(prompt or "").strip()
        if not prompt_text:
            raise ValueError("prompt_required")

        intent_id = f"intent-{uuid.uuid4().hex[:12]}"
        intent = {
            "intent_id": intent_id,
            "session_id": session_id,
            "user_id": user_id,
            "prompt": prompt_text,
            "received_at": now_iso(),
            "compiler": {
                "provider": str(AI_PROVIDER or "fallback"),
                "model": get_ai_route_model("intent_compile", fallback_model=AI_OLLAMA_MODEL),
                "prompt_template_version": "intent-v1",
                "confidence": 0.55,
            },
        }
        emit_trace(
            build_trace_event(
                event_type="ai_output",
                run_id=intent_id,
                outcome="success",
                payload={"intent_stage": "intent_received", "intent_id": intent_id, "prompt": prompt_text},
                model=_intent_trace_model(use_model=bool(use_model)),
            )
        )

        compiled = self._compile_prompt_to_steps(prompt_text, use_model=bool(use_model))
        run_spec_id = f"runspec-{uuid.uuid4().hex[:12]}"
        run_spec = {
            "run_spec_id": run_spec_id,
            "intent_id": intent_id,
            "scope": scope if scope in {"session", "task", "chunk"} else "session",
            "approval_mode": approval_mode if approval_mode in {"auto", "review_required"} else "review_required",
            "target": {
                "target_type": compiled.get("target_type") or "software.pty",
                "target_id": compiled.get("target_id"),
                "session_id": session_id,
                "task_id": None,
                "chunk_id": None,
            },
            "constraints": {
                "max_steps": 8,
                "max_runtime_seconds": 180,
                "max_budget_cost": 25,
                "allow_network": False,
                "allow_fs_write": False,
            },
            "transforms": compiled.get("transforms") or [],
            "steps": compiled.get("steps") or [],
        }
        decisions = self._validate_run_spec(run_spec)
        run_spec["decisions"] = decisions

        if decisions["allowed_steps"] == 0:
            intent["compiler"]["confidence"] = 0.2
        elif decisions["denied_steps"] == 0:
            intent["compiler"]["confidence"] = 0.82

        with self._lock:
            self._intents[intent_id] = intent
            self._run_specs[run_spec_id] = run_spec

        emit_trace(
            build_trace_event(
                event_type="ai_output",
                run_id=intent_id,
                outcome="success",
                payload={
                    "intent_stage": "intent_compiled",
                    "intent_id": intent_id,
                    "run_spec_id": run_spec_id,
                    "steps_total": len(run_spec["steps"]),
                    "allowed_steps": decisions["allowed_steps"],
                    "denied_steps": decisions["denied_steps"],
                },
                model=_intent_trace_model(use_model=bool(use_model)),
            )
        )
        return {"intent": intent, "run_spec": run_spec}

    def apply_run_spec(self, run_spec_id):
        run_spec = self._run_specs.get(str(run_spec_id))
        if not run_spec:
            raise KeyError("run_spec_not_found")

        runtime_outcome = self._apply_via_runtime_task(run_spec)
        if runtime_outcome is not None:
            with self._lock:
                self._outcomes[runtime_outcome["intent_id"]] = runtime_outcome
            self._emit_intent_outcome(run_spec, runtime_outcome)
            return runtime_outcome

        started_at = now_iso()
        results = []
        target_id = run_spec["target"].get("target_id")
        runtime_context = self._begin_runtime_envelope(run_spec)
        task_id = runtime_context.get("task_id")
        agent_id = runtime_context.get("agent_id")
        for step in run_spec["steps"]:
            step_id = step["step_id"]
            decision = step.get("decision") or {"allowed": False, "reason": "missing_decision"}
            if not decision.get("allowed"):
                results.append({"step_id": step_id, "status": "denied", "reason": decision.get("reason")})
                continue

            tool_id = step.get("tool_id")
            action = step.get("action")
            args = dict(step.get("args") or {})
            try:
                if tool_id == "terminal.session" and action == "open":
                    target = self.core.terminal_service.open_target(
                        {
                            "target_id": args.get("target_id"),
                            "target_type": args.get("target_type"),
                            "session_id": self.core.session_id,
                            "agent_id": agent_id,
                            "transport": args.get("transport"),
                            "meta": args.get("meta"),
                        },
                        trace_context=runtime_context,
                    )
                    target_id = target["target_id"]
                    run_spec["target"]["target_id"] = target_id
                    results.append({"step_id": step_id, "status": "applied", "target_id": target_id})
                elif tool_id == "terminal.transform" and action == "apply":
                    if not target_id:
                        raise ValueError("missing_target_for_transform")
                    updated = self.core.terminal_service.set_transform(
                        target_id=target_id,
                        module=str(args.get("module") or ""),
                        config=args.get("config") or {},
                        trace_context=runtime_context,
                    )
                    results.append({"step_id": step_id, "status": "applied", "target_id": updated["target_id"]})
                elif tool_id == "terminal.exec" and action == "run":
                    if not target_id:
                        raise ValueError("missing_target_for_exec")
                    output = self.core.terminal_service.run_command(
                        target_id=target_id,
                        command=str(args.get("command") or ""),
                        cwd=args.get("cwd"),
                        env=args.get("env"),
                        timeout_sec=int(args.get("timeout_sec") or 30),
                        trace_context=runtime_context,
                    )
                    results.append(
                        {
                            "step_id": step_id,
                            "status": "applied",
                            "exit_code": output.get("exit_code"),
                            "run_id": output.get("run_id"),
                        }
                    )
                else:
                    raise ValueError("unsupported_step")
            except Exception as exc:
                results.append({"step_id": step_id, "status": "failed", "reason": str(exc)})

        outcome = self._build_outcome(run_spec, results, started_at=started_at)
        outcome["task_id"] = task_id
        outcome["agent_id"] = agent_id
        outcome["execution_path"] = "inline_fallback"
        self._finalize_runtime_envelope(task_id, outcome["status"])
        with self._lock:
            self._outcomes[outcome["intent_id"]] = outcome
        self._emit_intent_outcome(run_spec, outcome)
        return outcome

    def get_run_spec(self, run_spec_id):
        return self._run_specs.get(str(run_spec_id))

    def _build_outcome(self, run_spec, results, started_at):
        applied = sum(1 for item in results if item.get("status") == "applied")
        failed = sum(1 for item in results if item.get("status") == "failed")
        denied = sum(1 for item in results if item.get("status") == "denied")
        if failed > 0 and applied > 0:
            status = "partially_applied"
        elif failed > 0:
            status = "failed"
        elif applied > 0:
            status = "completed"
        else:
            status = "canceled"
        return {
            "intent_id": run_spec["intent_id"],
            "run_spec_id": run_spec["run_spec_id"],
            "status": status,
            "started_at": started_at,
            "ended_at": now_iso(),
            "applied_steps": applied,
            "failed_steps": failed,
            "denied_steps": denied,
            "summary": f"Applied {applied} steps, failed {failed}, denied {denied}.",
            "next_actions": _next_actions(status),
            "results": results,
        }

    def _validate_run_spec(self, run_spec):
        allowed_steps = 0
        denied_steps = 0
        reasons = []
        for step in run_spec["steps"]:
            decision = {"allowed": True, "reason": "authorized"}
            tool_id = step.get("tool_id")
            action = step.get("action")
            args = dict(step.get("args") or {})
            if tool_id not in {"terminal.session", "terminal.exec", "terminal.transform"}:
                decision = {"allowed": False, "reason": "tool_not_allowed"}
            elif tool_id == "terminal.session" and action not in {"open"}:
                decision = {"allowed": False, "reason": "terminal_session_action_not_allowed"}
            elif tool_id == "terminal.transform" and action != "apply":
                decision = {"allowed": False, "reason": "terminal_transform_action_not_allowed"}
            elif tool_id == "terminal.exec" and action != "run":
                decision = {"allowed": False, "reason": "terminal_exec_action_not_allowed"}
            elif tool_id == "terminal.exec":
                cmd = str(args.get("command") or "")
                if not cmd.strip():
                    decision = {"allowed": False, "reason": "command_required"}
            step["decision"] = decision
            if decision["allowed"]:
                allowed_steps += 1
            else:
                denied_steps += 1
                reasons.append({"step_id": step.get("step_id"), "reason": decision["reason"]})
        emit_trace(
            build_trace_event(
                event_type="ai_output",
                run_id=run_spec["intent_id"],
                outcome="success",
                payload={
                    "intent_stage": "intent_validated",
                    "intent_id": run_spec["intent_id"],
                    "run_spec_id": run_spec["run_spec_id"],
                    "allowed_steps": allowed_steps,
                    "denied_steps": denied_steps,
                    "reasons": reasons,
                },
                model=_intent_trace_model(use_model=True),
            )
        )
        return {"allowed_steps": allowed_steps, "denied_steps": denied_steps, "reasons": reasons}

    def _begin_runtime_envelope(self, run_spec):
        task_id = None
        agent_id = f"intent-worker-{uuid.uuid4().hex[:8]}"
        try:
            task_name = f"intent:{str(run_spec.get('run_spec_id') or 'run')[:12]}"
            created_task_id = Task.create(
                name=task_name,
                execution_mode="sequential",
                has_conditionals=False,
                session_id=self.core.session_id,
            )
            task_row = Task.get_by_id(created_task_id)
            if task_row:
                runtime_task = hydrate_task(task_row, session_id=self.core.session_id, capabilities=["ai", "terminal"])
                runtime_task.status = TaskStatus.RUNNING
                self.core.task_objects[created_task_id] = runtime_task
                self.core.task_name_map.setdefault(runtime_task.name, []).append(created_task_id)
                task_id = created_task_id
        except Exception:
            task_id = None
        return {
            "task_id": task_id,
            "agent_id": agent_id,
            "chunk_id": None,
            "zone_id": None,
        }

    def _finalize_runtime_envelope(self, task_id, outcome_status):
        if not task_id:
            return
        task = self.core.task_objects.get(task_id)
        if not task:
            return
        if outcome_status == "completed":
            task.status = TaskStatus.COMPLETED
        elif outcome_status == "partially_applied":
            task.status = TaskStatus.BLOCKED
        elif outcome_status == "failed":
            task.status = TaskStatus.FAILED
        else:
            task.status = TaskStatus.TERMINATED

    def _apply_via_runtime_task(self, run_spec):
        try:
            started_at = now_iso()
            denied_results = []
            for step in run_spec.get("steps", []):
                decision = step.get("decision") or {}
                if not decision.get("allowed"):
                    denied_results.append(
                        {"step_id": step.get("step_id"), "status": "denied", "reason": decision.get("reason")}
                    )

            allowed_steps = [step for step in run_spec.get("steps", []) if (step.get("decision") or {}).get("allowed")]
            if not allowed_steps:
                outcome = self._build_outcome(run_spec, denied_results, started_at=started_at)
                outcome["execution_path"] = "runtime_task"
                outcome["task_id"] = None
                outcome["agent_id"] = None
                return outcome

            worker = self._ensure_runtime_terminal_worker(run_spec)
            agent_id = worker["agent_id"]
            task_intent = self._build_runtime_task_intent(
                run_spec,
                allowed_steps,
                agent_id,
                worker_capability=worker["worker_capability"],
            )
            task_id = self.core.ingest_intent(task_intent)
            run_spec["target"]["task_id"] = task_id

            completed = self._wait_for_task_completion(
                task_id=task_id,
                timeout_sec=int(run_spec.get("constraints", {}).get("max_runtime_seconds") or 180),
            )
            if not completed:
                task = self.core.task_objects.get(task_id)
                if task:
                    task.status = TaskStatus.TERMINATED

            step_results = self._build_results_from_runtime_task(task_id, allowed_steps)
            results = denied_results + step_results
            outcome = self._build_outcome(run_spec, results, started_at=started_at)
            outcome["execution_path"] = "runtime_task"
            outcome["task_id"] = task_id
            outcome["agent_id"] = agent_id
            outcome["worker_scope"] = worker["scope"]
            outcome["worker_reused"] = worker["reused"]
            if not completed:
                outcome["status"] = "failed"
                outcome["summary"] = f"{outcome['summary']} Runtime execution timeout."
            return outcome
        except Exception:
            return None

    def _ensure_runtime_terminal_worker(self, run_spec):
        scope = _worker_scope_for_target(run_spec.get("target", {}).get("target_id"))
        reusable = self._find_reusable_terminal_worker(scope)
        if reusable is not None:
            self._ensure_worker_name(reusable, scope)
            worker_capability = self._ensure_worker_capability(reusable)
            return {
                "agent_id": reusable.agent_id,
                "scope": scope,
                "worker_capability": worker_capability,
                "reused": True,
            }

        from hiveos.agents_sim.sim_terminal_agent import SimTerminalAgent

        if scope == "session":
            session_suffix = str(self.core.session_id or "global").replace("-", "")[:8] or "global"
            agent_id = f"{SESSION_TERM_WORKER_PREFIX}{session_suffix}"
            worker_name = SESSION_TERM_WORKER_NAME
        else:
            agent_id = f"{SCOPED_TERM_WORKER_PREFIX}{uuid.uuid4().hex[:8]}"
            worker_name = SCOPED_TERM_WORKER_NAME

        agent = SimTerminalAgent(agent_id=agent_id, name=worker_name)
        self.core.inject_agent(agent)
        runtime_agent = self.core._runtime_agents.get(agent.agent_id)
        worker_capability = self._ensure_worker_capability(runtime_agent) if runtime_agent else f"terminal.worker.{agent.agent_id}"
        return {
            "agent_id": agent.agent_id,
            "scope": scope,
            "worker_capability": worker_capability,
            "reused": False,
        }

    def _find_reusable_terminal_worker(self, scope):
        if scope == "session":
            for runtime_agent in self.core._runtime_agents.values():
                if runtime_agent.session_id != self.core.session_id:
                    continue
                if runtime_agent.agent_id.startswith(SESSION_TERM_WORKER_PREFIX):
                    if self._has_terminal_capability(runtime_agent):
                        return runtime_agent
            return None

        idle_scoped = []
        for runtime_agent in self.core._runtime_agents.values():
            if runtime_agent.session_id != self.core.session_id:
                continue
            if not runtime_agent.agent_id.startswith(SCOPED_TERM_WORKER_PREFIX):
                continue
            if not self._has_terminal_capability(runtime_agent):
                continue
            if self._is_idle(runtime_agent):
                idle_scoped.append(runtime_agent)
        if idle_scoped:
            return idle_scoped[0]
        return None

    def _has_terminal_capability(self, runtime_agent):
        caps = list(getattr(getattr(runtime_agent, "obj", None), "capabilities", []) or [])
        return "terminal" in caps

    def _is_idle(self, runtime_agent):
        status = getattr(getattr(runtime_agent, "obj", None), "status", None)
        return status in {AgentStatus.IDLE, "idle"}

    def _ensure_worker_capability(self, runtime_agent):
        if runtime_agent is None:
            return ""
        worker_capability = f"terminal.worker.{runtime_agent.agent_id}"
        obj = getattr(runtime_agent, "obj", None)
        caps = list(getattr(obj, "capabilities", []) or [])
        if worker_capability not in caps:
            caps.append(worker_capability)
            if obj is not None:
                obj.capabilities = caps
        return worker_capability

    def _ensure_worker_name(self, runtime_agent, scope):
        if runtime_agent is None:
            return
        obj = getattr(runtime_agent, "obj", None)
        if obj is None:
            return
        preferred = SESSION_TERM_WORKER_NAME if scope == "session" else SCOPED_TERM_WORKER_NAME
        current = str(getattr(obj, "name", "") or "").strip()
        if current != preferred:
            obj.name = preferred

    def _build_runtime_task_intent(self, run_spec, allowed_steps, agent_id, worker_capability):
        target_id = run_spec.get("target", {}).get("target_id")
        required_worker_cap = str(worker_capability or "").strip() or "terminal"
        chunks = []
        previous_chunk = None
        for index, step in enumerate(allowed_steps):
            step_args = dict(step.get("args") or {})
            if target_id and step.get("tool_id") in {"terminal.transform", "terminal.exec"}:
                step_args.setdefault("target_id", target_id)
            chunk_id = f"intent_step_{index + 1}"
            chunk_payload = {
                "intent": "terminal_action",
                "tool_id": step.get("tool_id"),
                "action": step.get("action"),
                "args": step_args,
                "trace_context": {"agent_id": agent_id},
                "step_id": step.get("step_id"),
                "run_spec_id": run_spec.get("run_spec_id"),
            }
            chunk_spec = {
                "chunk_id": chunk_id,
                "required_capability": required_worker_cap,
                "payload": chunk_payload,
                "ttl": 1,
                "timing": "ticks",
            }
            if previous_chunk:
                chunk_spec["depends_on"] = previous_chunk
            previous_chunk = chunk_id
            chunks.append(chunk_spec)

        return {
            "task_name": f"intent-runtime-{str(run_spec.get('run_spec_id') or 'run')[:8]}",
            "execution_mode": "sequential",
            "capability_required": [required_worker_cap],
            "chunks": chunks,
        }

    def _wait_for_task_completion(self, task_id, timeout_sec):
        timeout = max(1, int(timeout_sec))
        deadline = time.time() + timeout
        while time.time() < deadline:
            task = self.core.task_objects.get(task_id)
            if not task:
                return False
            if task.status in {TaskStatus.COMPLETED, TaskStatus.FAILED, TaskStatus.BLOCKED, TaskStatus.TERMINATED}:
                return True
            time.sleep(0.05)
        return False

    def _build_results_from_runtime_task(self, task_id, allowed_steps):
        by_step_id = {}
        for chunk in self.core.chunk_objects.values():
            if chunk.task_id != task_id:
                continue
            payload = chunk.payload if isinstance(chunk.payload, dict) else {}
            step_id = payload.get("step_id")
            if not step_id:
                continue
            if chunk.status == "completed":
                by_step_id[step_id] = {"step_id": step_id, "status": "applied"}
            elif chunk.status == "stale":
                by_step_id[step_id] = {
                    "step_id": step_id,
                    "status": "failed",
                    "reason": "runtime_chunk_stale",
                }
            else:
                by_step_id[step_id] = {
                    "step_id": step_id,
                    "status": "failed",
                    "reason": f"runtime_chunk_{chunk.status}",
                }
        ordered = []
        for step in allowed_steps:
            step_id = step.get("step_id")
            ordered.append(by_step_id.get(step_id) or {"step_id": step_id, "status": "failed", "reason": "missing_runtime_chunk"})
        return ordered

    def _emit_intent_outcome(self, run_spec, outcome):
        emit_trace(
            build_trace_event(
                event_type="ai_output",
                run_id=run_spec["intent_id"],
                outcome="success" if outcome["status"] == "completed" else "failed",
                payload={
                    "intent_stage": "intent_outcome",
                    "intent_id": outcome["intent_id"],
                    "run_spec_id": outcome["run_spec_id"],
                    "task_id": outcome.get("task_id"),
                    "agent_id": outcome.get("agent_id"),
                    "execution_path": outcome.get("execution_path", "inline_fallback"),
                    "status": outcome["status"],
                    "applied_steps": outcome["applied_steps"],
                    "failed_steps": outcome["failed_steps"],
                    "denied_steps": outcome["denied_steps"],
                },
                task_id=outcome.get("task_id"),
                agent_id=outcome.get("agent_id"),
                model=_intent_trace_model(use_model=True),
            )
        )

    def _compile_prompt_to_steps(self, prompt_text, use_model):
        model_plan = {}
        if use_model and str(AI_PROVIDER or "").lower() == "ollama":
            model_name = get_ai_route_model("intent_compile", fallback_model=AI_OLLAMA_MODEL)
            model_plan = _compile_with_ollama(prompt_text, model_name=model_name) or {}
        if model_plan.get("steps"):
            return _normalize_plan(model_plan, prompt_text)
        return _fallback_plan(prompt_text)


def _compile_with_ollama(prompt_text, model_name=None):
    model = str(model_name or get_ai_route_model("intent_compile", fallback_model=AI_OLLAMA_MODEL) or "phi3")
    timeout = max(6, min(25, int(AI_OLLAMA_TIMEOUT_SEC or 40)))
    instructions = (
        "You compile operator intent to a strict JSON plan for terminal orchestration.\n"
        "Return only JSON with keys: target_type, target_id, transforms, steps.\n"
        "steps items must include: tool_id, action, args.\n"
        "Allowed tools/actions: terminal.session/open, terminal.transform/apply, terminal.exec/run.\n"
        "If user asks translation/localization, include terminal.transform localize with config.language matching requested language.\n"
        "Never include markdown."
    )
    prompt = f"{instructions}\n\nOPERATOR_PROMPT:\n{prompt_text}"
    try:
        result = subprocess.run(
            ["ollama", "run", model, prompt],
            capture_output=True,
            text=True,
            encoding="utf-8",
            timeout=timeout,
        )
        payload = _extract_json_object((result.stdout or "").strip())
        return payload if isinstance(payload, dict) else None
    except Exception:
        return None


def _extract_json_object(text):
    source = str(text or "").strip()
    if not source:
        return None
    fenced = re.search(r"```json\s*([\s\S]+?)\s*```", source, re.IGNORECASE)
    if fenced:
        source = fenced.group(1).strip()
    try:
        return json.loads(source)
    except Exception:
        pass
    start = source.find("{")
    end = source.rfind("}")
    if start >= 0 and end > start:
        try:
            return json.loads(source[start : end + 1])
        except Exception:
            return None
    return None


def _normalize_plan(plan, prompt_text):
    target_type = str(plan.get("target_type") or "software.pty")
    target_id = str(plan.get("target_id") or "").strip() or f"intent-terminal-{uuid.uuid4().hex[:8]}"
    raw_steps = plan.get("steps")
    if not isinstance(raw_steps, list):
        return _fallback_plan(prompt_text)
    steps = []
    for index, raw in enumerate(raw_steps[:8]):
        if not isinstance(raw, dict):
            continue
        tool_id = str(raw.get("tool_id") or raw.get("tool") or "").strip()
        action = str(raw.get("action") or raw.get("op") or "").strip()
        args = raw.get("args") if isinstance(raw.get("args"), dict) else {}
        tool_id, action = _canonicalize_tool_and_action(tool_id, action, args)
        steps.append(
            {
                "step_id": f"step-{index + 1}",
                "depends_on": [] if index == 0 else [f"step-{index}"],
                "tool_id": tool_id,
                "action": action,
                "args": args,
                "risk_level": "medium" if tool_id == "terminal.exec" else "low",
            }
        )
    transforms = plan.get("transforms") if isinstance(plan.get("transforms"), list) else []
    requested_language = _extract_requested_language(prompt_text)
    if requested_language:
        for step in steps:
            if step.get("tool_id") == "terminal.transform" and step.get("action") == "apply":
                args = dict(step.get("args") or {})
                if str(args.get("module") or "").strip() == "localize":
                    config = dict(args.get("config") or {})
                    config["language"] = requested_language
                    args["config"] = config
                    step["args"] = args
        for transform in transforms:
            if not isinstance(transform, dict):
                continue
            if str(transform.get("module") or "").strip() == "localize":
                config = dict(transform.get("config") or {})
                config["language"] = requested_language
                transform["config"] = config
    if not any(step.get("tool_id") in {"terminal.session", "terminal.exec", "terminal.transform"} for step in steps):
        return _fallback_plan(prompt_text)
    return _apply_terminal_target_preference(
        {"target_type": target_type, "target_id": target_id, "steps": steps, "transforms": transforms},
        prompt_text,
    )


def _fallback_plan(prompt_text):
    lowered = str(prompt_text or "").lower()
    target_type = "hardware.bridge" if any(word in lowered for word in ("hardware", "serial", "ble", "device")) else "software.pty"
    target_id = f"intent-terminal-{uuid.uuid4().hex[:8]}"
    command = _extract_command(prompt_text)
    steps = [
        {
            "step_id": "step-1",
            "depends_on": [],
            "tool_id": "terminal.session",
            "action": "open",
            "args": {
                "target_id": target_id,
                "target_type": target_type,
                "transport": {"kind": "serial" if target_type == "hardware.bridge" else "pty", "ref": "auto"},
                "meta": {"label": "Intent Terminal"},
            },
            "risk_level": "low",
        }
    ]
    transforms = []
    requested_language = _extract_requested_language(prompt_text)
    wants_localize = "translate" in lowered or "localize" in lowered or bool(requested_language)
    localize_language = requested_language or "italian"
    if wants_localize:
        steps.append(
            {
                "step_id": "step-2",
                "depends_on": ["step-1"],
                "tool_id": "terminal.transform",
                "action": "apply",
                "args": {"module": "localize", "config": {"language": localize_language}},
                "risk_level": "low",
            }
        )
        exec_dep = "step-2"
    elif "tutor" in lowered or "explain" in lowered:
        steps.append(
            {
                "step_id": "step-2",
                "depends_on": ["step-1"],
                "tool_id": "terminal.transform",
                "action": "apply",
                "args": {"module": "tutor", "config": {}},
                "risk_level": "low",
            }
        )
        exec_dep = "step-2"
    else:
        exec_dep = "step-1"

    steps.append(
        {
            "step_id": f"step-{len(steps) + 1}",
            "depends_on": [exec_dep],
            "tool_id": "terminal.exec",
            "action": "run",
            "args": {"command": command, "timeout_sec": 45},
            "risk_level": "medium",
        }
    )
    if any("localize" == (step.get("args") or {}).get("module") for step in steps):
        transforms.append({"module": "localize", "config": {"language": localize_language}})
    if any("tutor" == (step.get("args") or {}).get("module") for step in steps):
        transforms.append({"module": "tutor", "config": {}})
    return _apply_terminal_target_preference(
        {"target_type": target_type, "target_id": target_id, "steps": steps, "transforms": transforms},
        prompt_text,
    )


def _apply_terminal_target_preference(plan, prompt_text):
    preferred_target_id = _preferred_terminal_target_id(prompt_text)
    if not preferred_target_id:
        return plan

    target_type = str(plan.get("target_type") or "software.pty")
    if target_type == "hardware.bridge":
        return plan

    updated = {
        "target_type": "software.pty",
        "target_id": preferred_target_id,
        "steps": [],
        "transforms": list(plan.get("transforms") or []),
    }
    for step in list(plan.get("steps") or []):
        if not isinstance(step, dict):
            continue
        next_step = dict(step)
        next_args = dict(next_step.get("args") or {})
        if next_step.get("tool_id") == "terminal.session" and next_step.get("action") == "open":
            next_args["target_id"] = preferred_target_id
            next_args["target_type"] = "software.pty"
            next_step["args"] = next_args
        updated["steps"].append(next_step)
    return updated


def _preferred_terminal_target_id(prompt_text):
    lowered = str(prompt_text or "").lower()
    if any(phrase in lowered for phrase in ("session terminal", "session scoped terminal", "global terminal")):
        return SESSION_TERMINAL_TARGET_ID
    return None


def _worker_scope_for_target(target_id):
    if str(target_id or "").strip() == SESSION_TERMINAL_TARGET_ID:
        return "session"
    return "scoped"


def _extract_command(prompt_text):
    text = str(prompt_text or "").strip()
    if not text:
        return 'echo "intent compiled and executed"'

    # Prefer explicit command forms so quoted arguments are preserved.
    command_patterns = [
        r"\b(echo\s+\"[^\"]+\"(?:\s+.*)?)",
        r"\b(echo\s+'[^']+'(?:\s+.*)?)",
        r"\b(echo\s+\S(?:.*)?)",
        r"\b(python(?:3)?\s+.+)",
        r"\b(pip\s+.+)",
        r"\b(npm\s+.+)",
        r"\b(git\s+.+)",
        r"\b(cmd\s+/c\s+.+)",
        r"\b(powershell(?:\.exe)?\s+.+)",
    ]
    for pattern in command_patterns:
        match = re.search(pattern, text, re.IGNORECASE)
        if match:
            return match.group(1).strip()

    quoted = re.search(r'"([^"]+)"', text)
    if quoted:
        return quoted.group(1).strip()

    if " and " in text.lower():
        tail = text.split(" and ", 1)[1].strip()
        if tail:
            return tail
    return 'echo "intent compiled and executed"'


def _extract_requested_language(prompt_text):
    text = str(prompt_text or "").strip().lower()
    if not text:
        return ""

    direct_map = {
        "italian": "italian",
        "french": "french",
        "spanish": "spanish",
        "german": "german",
        "portuguese": "portuguese",
        "japanese": "japanese",
        "korean": "korean",
        "chinese": "chinese",
        "english": "english",
    }
    for token, normalized in direct_map.items():
        if re.search(rf"\b{re.escape(token)}\b", text):
            return normalized

    to_match = re.search(r"\b(?:to|into)\s+([a-zA-Z]+)\b", text)
    if to_match:
        candidate = to_match.group(1).strip().lower()
        if candidate in direct_map:
            return direct_map[candidate]
    return ""


def _next_actions(status):
    if status == "completed":
        return ["Inspect output and proceed to next workflow step."]
    if status == "partially_applied":
        return ["Review failed steps and re-run with narrower scope."]
    if status == "failed":
        return ["Open review mode and approve an adjusted plan."]
    return ["Compile a revised prompt with explicit command details."]


def _intent_trace_model(use_model):
    provider = str(AI_PROVIDER or "").strip().lower()
    configured_model = get_ai_route_model("intent_compile", fallback_model=AI_OLLAMA_MODEL)
    if not use_model:
        return {"provider": "deterministic", "name": "rule_based"}
    if provider == "ollama":
        return {"provider": "ollama", "name": configured_model or "phi3"}
    if provider:
        return {"provider": provider, "name": configured_model or "fallback"}
    return {"provider": "fallback", "name": configured_model or "fallback"}


def _canonicalize_tool_and_action(tool_id, action, args):
    tool = str(tool_id or "").strip().lower()
    act = str(action or "").strip().lower()

    if "." not in tool and ":" in tool:
        tool = tool.replace(":", ".")
    if tool in {"terminal.open", "terminal.create"}:
        return "terminal.session", "open"
    if tool in {"terminal.close", "terminal.destroy"}:
        return "terminal.session", "close"
    if tool in {"terminal.run", "terminal.execute", "terminal.command", "terminal.cmd"}:
        return "terminal.exec", "run"
    if tool in {"terminal.localized", "terminal.tutor", "terminal.hardware"}:
        return "terminal.transform", "apply"

    if tool == "terminal":
        if act in {"open", "create", "start"}:
            return "terminal.session", "open"
        if act in {"close", "stop"}:
            return "terminal.session", "close"
        if act in {"run", "exec", "execute", "command", "cmd"}:
            return "terminal.exec", "run"
        if act in {"transform", "apply_transform", "translate", "localize", "tutor", "explain"}:
            return "terminal.transform", "apply"

    if tool == "terminal.session" and act in {"create", "start"}:
        act = "open"
    elif tool == "terminal.exec" and act in {"exec", "execute", "command", "cmd"}:
        act = "run"
    elif tool == "terminal.transform" and act in {"transform", "set", "configure"}:
        act = "apply"

    # If tool is omitted but args clearly indicate terminal execution, fill defaults.
    if not tool and str(args.get("command") or "").strip():
        return "terminal.exec", "run"

    return tool or str(tool_id or "").strip().lower(), act or str(action or "").strip().lower()
