"""Condensa Decoder — Condensa format → Natural language.

Converts compact Condensa messages back to human-readable natural language
for inspection and debugging.

v0.3: !? sync commands (!! still accepted), /each /scan /repeat:N modifiers
      (old ' \\ ^:N still accepted), // fallback parsing kept for backward compat,
      $_ reference, [] inline eval, ? safe access, @:policy, <<<>>> bulk,
      bare-verb pipelines.
"""

import re
from .utils import (
    MSG_TYPES_REV, ACTIONS_REV, URGENCY_REV, CONFIDENCE_REV,
    KNOWN_ACTIONS, IMPLICIT_VARS,
    count_tokens,
)


class CondensaDecoder:
    """Converts Condensa format back to natural language."""

    def decode(self, condensa: str) -> str:
        """Decode a Condensa message to natural language."""
        text = condensa.strip()

        # Extract urgency prefix
        urgency = ""
        for prefix in ["+++", "++", "--", "+", "-"]:
            if text.startswith(prefix) and not text.startswith(prefix + ":"):
                urgency_label = URGENCY_REV.get(prefix, "")
                if urgency_label:
                    urgency = f"[{urgency_label.upper()}] "
                text = text[len(prefix):]
                break

        # Handle flow control wrappers
        if text.startswith("seq(") and text.endswith(")"):
            return urgency + self._decode_flow("sequential", text[4:-1])
        if text.startswith("par(") and text.endswith(")"):
            return urgency + self._decode_flow("parallel", text[4:-1])

        # Handle pipeline
        if " | " in text:
            return urgency + self._decode_pipeline(text)

        # Parse type:body — must check !? and !! before ! (longest match first)
        if text.startswith("!?:"):
            msg_type = "sync_command"
            body = text[3:].strip()
        elif text.startswith("!!:"):  # v0.2 backward compat
            msg_type = "sync_command"
            body = text[3:].strip()
        elif len(text) >= 2 and text[1] == ":":
            msg_type_char = text[0]
            body = text[2:].strip()
            msg_type = MSG_TYPES_REV.get(msg_type_char, "unknown")
        else:
            return urgency + f"[unparseable: {text}]"

        decoder_map = {
            "command": self._decode_command,
            "sync_command": self._decode_sync_command,
            "query": self._decode_query,
            "result": self._decode_result,
            "status": self._decode_status,
            "error": self._decode_error,
            "cancel": self._decode_cancel,
            "update": self._decode_update,
            "meta": self._decode_meta,
            "delegate": self._decode_delegate,
        }

        decoder = decoder_map.get(msg_type, self._decode_generic)
        return urgency + decoder(body)

    def decode_with_stats(self, condensa: str) -> dict:
        """Decode and return stats."""
        nl = self.decode(condensa)
        c_tokens = count_tokens(condensa)
        nl_tokens = count_tokens(nl)
        return {
            "condensa": condensa,
            "natural_language": nl,
            "condensa_tokens": c_tokens,
            "nl_tokens": nl_tokens,
            "expansion_ratio": round(nl_tokens / c_tokens, 2) if c_tokens > 0 else 0,
        }

    # ── Type-specific decoders ───────────────────────────────────────

    def _decode_command(self, body: str) -> str:
        """Decode a command message."""
        parts = self._split_body(body)
        if not parts:
            return f"Execute: {body}"

        action_raw = parts[0]
        adverb_suffix = self._extract_adverb(action_raw)
        action_raw = self._strip_adverb(action_raw)

        action = ACTIONS_REV.get(action_raw, action_raw)
        args = parts[1:]

        result = f"{action.capitalize()}{adverb_suffix}"
        params, modifiers, fallback = self._categorize_args(args)

        if params:
            result += " " + ", ".join(self._format_param(p) for p in params)
        if modifiers:
            result += ". " + ". ".join(self._format_modifier(m) for m in modifiers)
        if fallback:
            result += f". On failure: {self._format_fallback(fallback)}"

        return result + "."

    def _decode_sync_command(self, body: str) -> str:
        """Decode a synchronous command — prefixes 'Synchronously' before the decoded command."""
        decoded = self._decode_command(body)
        return f"Synchronously {decoded[0].lower()}{decoded[1:]}"

    def _decode_query(self, body: str) -> str:
        parts = self._split_body(body)
        if not parts:
            return f"Query: {body}"
        action = ACTIONS_REV.get(parts[0], parts[0])
        args = " ".join(parts[1:])
        return f"Request: {action} {args}".strip() + "."

    def _decode_result(self, body: str) -> str:
        parts = self._split_body(body)
        confidence = ""
        # Check last part for confidence marker
        if parts and parts[-1] in CONFIDENCE_REV:
            conf_label = CONFIDENCE_REV[parts[-1]]
            confidence = f" (confidence: {conf_label})"
            parts = parts[:-1]
        return f"Result: {' '.join(parts)}{confidence}."

    def _decode_status(self, body: str) -> str:
        parts = self._split_body(body)
        if not parts:
            return f"Status: {body}"

        state_map = {"done": "completed", "run": "in progress", "wait": "waiting", "fail": "failed"}
        state = state_map.get(parts[0], parts[0])
        details = []

        for p in parts[1:]:
            if "/" in p and not p.startswith("/"):
                details.append(f"progress: {p}")
            elif ":" in p:
                k, v = p.split(":", 1)
                details.append(f"{k}: {v}")
            else:
                details.append(p)

        result = f"Status: {state}"
        if details:
            result += ". " + ". ".join(d.capitalize() for d in details)
        return result + "."

    def _decode_error(self, body: str) -> str:
        parts = self._split_body(body)
        if not parts:
            return f"Error: {body}"

        code = parts[0]
        details = []
        for p in parts[1:]:
            if p.startswith("retry:"):
                details.append(f"retry up to {p.split(':')[1]} times")
            elif p.startswith("wait:"):
                details.append(f"wait {p.split(':')[1]}")
            elif p.startswith("backoff:"):
                bo = p.split(":")[1]
                details.append(f"{'exponential' if bo == 'exp' else bo} backoff")
            elif p.startswith("fb:"):
                fb = p.split(":")[1]
                details.append(f"fallback: {self._format_fallback(p)}")
            else:
                details.append(p)

        result = f"Error {code}"
        if details:
            result += ". " + ". ".join(d.capitalize() for d in details)
        return result + "."

    def _decode_cancel(self, body: str) -> str:
        parts = self._split_body(body)
        target = parts[0] if parts else "current task"
        extras = []
        for p in parts[1:]:
            if p.startswith("discard:"):
                extras.append("discard partial results")
            elif p.startswith("safe:"):
                extras.append("ensure no side effects")
            elif p.startswith("ack:"):
                extras.append("confirm cancellation")
        result = f"Cancel {target}"
        if extras:
            result += ". " + ". ".join(e.capitalize() for e in extras)
        return result + "."

    def _decode_update(self, body: str) -> str:
        parts = self._split_body(body)
        if not parts:
            return f"Update context: {body}"

        op = parts[0]
        op_map = {"^": "Inherit and modify", "&": "Extend", "!&": "Replace"}
        result = op_map.get(op, "Update") + " context"

        changes = []
        for p in parts[1:]:
            if "->" in p:
                field_val = p.split(":")
                if len(field_val) == 2:
                    field = field_val[0]
                    old_new = field_val[1].split("->")
                    if len(old_new) == 2:
                        changes.append(f"{field}: {old_new[0]} → {old_new[1]}")
            elif ":" in p:
                k, v = p.split(":", 1)
                action_map = {"keep": "keep as-is", "del": "remove", "revise": "revise",
                             "revise+detail": "revise with more detail"}
                v = action_map.get(v, v)
                changes.append(f"{k}: {v}")

        if changes:
            result += ": " + ", ".join(changes)
        return result + "."

    def _decode_meta(self, body: str) -> str:
        parts = self._split_body(body)
        if not parts:
            return f"Meta: {body}"
        action = parts[0]
        if action == "define":
            return f"Define shorthand: {' '.join(parts[1:])}"
        elif action == "clarify":
            return f"Request clarification: {' '.join(parts[1:])}"
        elif action == "verbose":
            return "Enable verbose mode."
        elif action == "ack":
            return "Acknowledged."
        elif action == "policy":
            return f"Set session defaults: {', '.join(self._format_param(p) for p in parts[1:])}"
        return f"Meta ({action}): {' '.join(parts[1:])}"

    def _decode_delegate(self, body: str) -> str:
        parts = self._split_body(body)
        if not parts:
            return f"Delegate: {body}"
        target = parts[0]
        rest = " ".join(parts[1:])
        return f"Delegate to {target}: {self._decode_command(rest)}"

    def _decode_generic(self, body: str) -> str:
        return f"Message: {body}"

    # ── Flow control ─────────────────────────────────────────────────

    def _decode_flow(self, flow_type: str, inner: str) -> str:
        steps = [s.strip() for s in inner.split(";")]
        decoded_steps = []
        for i, step in enumerate(steps, 1):
            decoded = self.decode(step)
            decoded_steps.append(f"Step {i}: {decoded}")
        return f"Execute {flow_type}ly:\n" + "\n".join(decoded_steps)

    def _decode_pipeline(self, text: str) -> str:
        """Decode a pipeline, supporting bare-verb (implicit type) segments."""
        stages = [s.strip() for s in text.split(" | ")]
        decoded = []
        for s in stages:
            first_word = s.split()[0] if s.split() else ""
            # Strip adverb suffix for action lookup
            base_word = self._strip_adverb(first_word)
            if base_word.split("^")[0] in KNOWN_ACTIONS:
                s = "!:" + s  # bare verb — add type prefix for decoding
            decoded.append(self.decode(s))
        return "Pipeline: " + " → ".join(decoded)

    # ── Helpers ──────────────────────────────────────────────────────

    def _split_body(self, body: str) -> list[str]:
        """Split body respecting quoted strings, [] brackets, and <<<>>> bulk literals."""
        parts = []
        current = ""
        i = 0
        in_quote = False
        quote_char = ""

        while i < len(body):
            ch = body[i]

            # Handle <<<>>> bulk literals
            if body[i:i+3] == "<<<" and not in_quote:
                end_idx = body.find(">>>", i + 3)
                if end_idx != -1:
                    if current:
                        parts.append(current)
                        current = ""
                    parts.append(body[i:end_idx + 3])
                    i = end_idx + 3
                    continue

            # Handle [] inline evaluation
            if ch == "[" and not in_quote:
                bracket_depth = 1
                bracket_content = "["
                j = i + 1
                while j < len(body) and bracket_depth > 0:
                    if body[j] == "[":
                        bracket_depth += 1
                    elif body[j] == "]":
                        bracket_depth -= 1
                    bracket_content += body[j]
                    j += 1
                if current:
                    parts.append(current)
                    current = ""
                parts.append(bracket_content)
                i = j
                continue

            # Handle quotes
            if ch in ("'", '"') and not in_quote:
                in_quote = True
                quote_char = ch
                current += ch
            elif in_quote and ch == quote_char:
                in_quote = False
                current += ch
            elif ch == " " and not in_quote:
                if current:
                    parts.append(current)
                current = ""
            else:
                current += ch
            i += 1

        if current:
            parts.append(current)
        return parts

    def _categorize_args(self, args: list[str]) -> tuple[list, list, str]:
        """Separate args into params, modifiers, and fallback.

        Handles // alternative operator: when '//' is encountered, the next
        token is the fallback value.
        """
        params = []
        modifiers = []
        fallback = ""
        i = 0
        while i < len(args):
            a = args[i]
            if a.startswith("fb:"):
                fallback = a
            elif a == "//" and i + 1 < len(args):
                # // alternative operator — next arg is fallback value
                fallback = f"fb:{args[i + 1]}"
                i += 1
            elif a.startswith("/"):
                modifiers.append(a)
            else:
                params.append(a)
            i += 1
        return params, modifiers, fallback

    def _format_param(self, param: str) -> str:
        """Format a parameter for display.

        Handles implicit vars ($_, $turn, $agent, $t), safe access (?),
        [] inline evaluation, and <<<>>> bulk literals.
        """
        # <<<>>> bulk literal — return content as opaque block
        if param.startswith("<<<") and param.endswith(">>>"):
            return param[3:-3]

        # [] inline evaluation — recursively decode inner content
        if param.startswith("[") and param.endswith("]"):
            inner = param[1:-1]
            return f"({self.decode(inner)})"

        # Safe access — ? suffix means "if exists"
        if param.endswith("?"):
            inner = param[:-1]
            formatted = self._format_param(inner)
            return f"{formatted} (if exists)"

        # Implicit context variables
        if param in IMPLICIT_VARS:
            var_map = {
                "$_": "the previous result",
                "$turn": "turn number",
                "$agent": "current agent",
                "$t": "current timestamp",
            }
            return var_map.get(param, param)

        if ":" in param:
            k, v = param.split(":", 1)
            return f"{k}={v}"
        return param.strip("'\"")

    def _format_modifier(self, mod: str) -> str:
        mod = mod.lstrip("/")
        if ":" in mod:
            k, v = mod.split(":", 1)
            mod_map = {
                "n": f"Limit to {v} results",
                "fmt": f"Format as {v}",
                "lang": f"Language: {v}",
                "since": f"Since {v}",
                "timeout": f"Timeout: {v}",
                "repeat": f"Repeat {v} times",
            }
            return mod_map.get(k, f"{k}: {v}")
        mod_map = {
            "desc": "Sort descending", "asc": "Sort ascending",
            "top": "Top results", "bot": "Bottom results",
            "each": "Apply to each", "scan": "Apply cumulatively",
        }
        return mod_map.get(mod, mod)

    def _format_fallback(self, fb: str) -> str:
        fb_part = fb.split(":")[1] if ":" in fb else fb
        fb_map = {
            "cache": "use cached data",
            "skip": "skip and continue",
            "abort": "abort operation",
            "default": "use default value",
            "ask": "ask for clarification",
            "degrade": "return partial/degraded result",
        }
        if fb_part.startswith("retry:"):
            n = fb_part.split(":")[1]
            return f"retry {n} times"
        return fb_map.get(fb_part, fb_part)

    # ── Adverb helpers ───────────────────────────────────────────────

    def _extract_adverb(self, action_raw: str) -> str:
        """Extract adverb suffix string from a raw action token."""
        if action_raw.endswith("'"):
            return " each"
        elif action_raw.endswith("\\"):
            return " cumulatively"
        elif "^:" in action_raw:
            _, n = action_raw.split("^:", 1)
            return f" (repeat {n} times)"
        return ""

    def _strip_adverb(self, action_raw: str) -> str:
        """Strip adverb suffix from a raw action token, returning the base verb."""
        if action_raw.endswith("'"):
            return action_raw[:-1]
        elif action_raw.endswith("\\"):
            return action_raw[:-1]
        elif "^:" in action_raw:
            verb, _ = action_raw.split("^:", 1)
            return verb
        return action_raw


def decode(condensa: str) -> str:
    """Convenience function to decode Condensa to natural language."""
    return CondensaDecoder().decode(condensa)


def decode_with_stats(condensa: str) -> dict:
    """Convenience function to decode with stats."""
    return CondensaDecoder().decode_with_stats(condensa)
