#
#   Imandra Inc.
#
#   speclogician/commands/utils.py
#

from __future__ import annotations
from collections.abc import Mapping, Sequence

import json
from pathlib import Path
from typing import TypeVar

import typer
from ..utils.load import load_state
from ..state.change import StateChange
from ..state.state import State

from ..modeling.scenario import ScenarioDelta

T = TypeVar("T", bound=StateChange)

def get_state(ctx: typer.Context, json_mode: bool = False) -> State:
    state = ctx.obj.state
    if state is None:
        path = getattr(ctx.obj, "state_json_path", None)
        state = load_state(path=path, json=json_mode)
        ctx.obj.state = state
    return state

def normalize_string_escapes(v: str) -> str:
    """
    Convert escaped sequences like '\\n', '\\t' into actual characters.
    """
    try:
        return v.encode("utf-8").decode("unicode_escape")
    except Exception:
        return v

def normalize_data(obj):
    if isinstance(obj, str):
        return normalize_string_escapes(obj)

    if isinstance(obj, Mapping):
        return {k: normalize_data(v) for k, v in obj.items()}

    if isinstance(obj, Sequence) and not isinstance(obj, (str, bytes)):
        return [normalize_data(v) for v in obj]

    return obj


def parse_scenario_delta(value: str | None) -> ScenarioDelta | None:
    """
    Parse a ScenarioDelta from CLI input.

    Accepted forms:
      1) None / "" -> None
      2) Comma-separated tokens:
           "+p1,+p2,-p3"   => add=["p1","p2"], remove=["p3"]
           "p1,p2"         => add=["p1","p2"], remove=[]
           "-p3"           => add=[], remove=["p3"]
      3) JSON:
           '{"add":["p1"],"remove":["p2"]}'
         or a path to a JSON file containing that object.

    Validation (dedup, identifier check, overlap) is enforced by ScenarioDelta itself.
    """
    if value is None:
        return None

    s = value.strip()
    if not s:
        return None

    # File path to JSON
    p = Path(s)
    if p.exists() and p.is_file():
        data = json.loads(p.read_text())
        delta = ScenarioDelta.model_validate(data)
        return None if delta.is_empty() else delta

    # Inline JSON
    if s.startswith("{"):
        data = json.loads(s)
        delta = ScenarioDelta.model_validate(data)
        return None if delta.is_empty() else delta

    # Token format: "+a,+b,-c" or "a,b"
    add: list[str] = []
    remove: list[str] = []

    for tok in (t.strip() for t in s.split(",") if t.strip()):
        if tok.startswith("+"):
            add.append(tok[1:].strip())
        elif tok.startswith("-"):
            remove.append(tok[1:].strip())
        else:
            # default: treat as add
            add.append(tok)

    delta = ScenarioDelta(add=add, remove=remove)
    return None if delta.is_empty() else delta