"""
feedback_logger.py — Editorial feedback capture + priors injection.

Layer 1: Capture — log_editorial_decision() → editorial_log.jsonl
Layer 2: Storage — per-project JSONL + hand-written priors JSON
Layer 3: Injection — get_editorial_priors() → prompt modifiers + param overrides
"""

import json
import re
import time
from pathlib import Path

from recoil.core.paths import ProjectPaths


def _slugify(text: str) -> str:
    """Normalize a freetext tag to a slug."""
    s = text.lower().strip()
    s = re.sub(r'[^a-z0-9]+', '_', s)
    return s.strip('_')


def _load_seed_tags() -> dict:
    """Load seed tags from config/editorial_tags.json."""
    tags_path = Path(__file__).parent.parent / "config" / "editorial_tags.json"
    if tags_path.exists():
        try:
            return json.loads(tags_path.read_text(encoding="utf-8"))
        except (json.JSONDecodeError, IOError):
            pass
    return {"rejection": [], "approval": []}


def normalize_tags(raw_tags: list[str]) -> list[str]:
    """Slugify tags and deduplicate against seed tags."""
    seed = _load_seed_tags()
    all_seeds = set(seed.get("rejection", []) + seed.get("approval", []))

    normalized = []
    for tag in raw_tags:
        slug = _slugify(tag)
        if not slug:
            continue
        if slug in all_seeds:
            normalized.append(slug)
        else:
            matched = False
            for seed_tag in all_seeds:
                if slug.startswith(seed_tag) or seed_tag.startswith(slug):
                    normalized.append(seed_tag)
                    matched = True
                    break
            if not matched:
                normalized.append(slug)

    return list(dict.fromkeys(normalized))


def log_editorial_decision(
    project: str,
    episode_id: str,
    shot_id: str,
    decision: str,
    stage: str,
    tags: list[str] = None,
    notes: str = "",
    pass_id: str = "",
    metadata: dict = None,
) -> Path:
    """Append a structured editorial decision to the project's JSONL log."""
    log_dir = ProjectPaths.for_project(project).visual_state_dir
    log_dir.mkdir(parents=True, exist_ok=True)
    log_path = log_dir / "editorial_log.jsonl"

    entry = {
        "timestamp": time.time(),
        "episode_id": episode_id,
        "shot_id": shot_id,
        "decision": decision,
        "stage": stage,
        "tags": normalize_tags(tags) if tags else [],
        "notes": notes,
        "pipeline": "visual",
    }
    if pass_id:
        entry["pass_id"] = pass_id
    if metadata:
        entry["metadata"] = metadata

    with open(log_path, "a", encoding="utf-8") as f:
        f.write(json.dumps(entry) + "\n")

    return log_path


def get_editorial_priors(project: str) -> dict:
    """Load hand-written editorial priors for prompt/parameter injection.

    Returns dict with:
      - prompt_modifiers_learned: dict[pattern, modifier_text]
      - parameter_overrides: dict[pattern, {cfg_scale, mode, ...}]
    """
    priors_path = ProjectPaths.for_project(project).visual_state_dir / "editorial_priors.json"
    if priors_path.exists():
        try:
            return json.loads(priors_path.read_text(encoding="utf-8"))
        except (json.JSONDecodeError, IOError):
            pass
    return {"prompt_modifiers_learned": {}, "parameter_overrides": {}}


def match_priors(
    priors: dict,
    focus_character: str = "",
    shot_type: str = "",
    location_id: str = "",
    is_env: bool = False,
) -> tuple[list[str], dict]:
    """Match editorial priors against shot attributes.

    Returns (prompt_modifiers, parameter_overrides).
    """
    prompt_mods: list[str] = []
    param_overrides: dict = {}

    attrs = {
        "char": focus_character.upper(),
        "shot_type": shot_type.upper(),
        "location": location_id.lower(),
        "env": str(is_env).lower(),
    }

    for pattern, modifier in priors.get("prompt_modifiers_learned", {}).items():
        if _pattern_matches(pattern, attrs):
            prompt_mods.append(modifier)

    for pattern, overrides in priors.get("parameter_overrides", {}).items():
        if _pattern_matches(pattern, attrs):
            allowed = {"cfg_scale", "add_expression_ref", "mode", "transition"}
            param_overrides = {k: v for k, v in overrides.items() if k in allowed}
            break

    return prompt_mods, param_overrides


def _pattern_matches(pattern: str, attrs: dict) -> bool:
    """Check if a pattern like 'char:TORCH/shot_type:CU' matches attributes."""
    for part in pattern.split("/"):
        if ":" not in part:
            continue
        key, value = part.split(":", 1)
        if key in attrs and attrs[key] and attrs[key].lower() != value.lower():
            return False
    return True
