from typing import List
from uuid import uuid4
from .playbooks.store import playbook_store, PlaybookItem

# Fields a caller can supply; anything else in a persist dict is ignored.
# `order` is deliberately excluded — see persist_playbook.
_EDITABLE_FIELDS = ("title", "goal", "instructions", "tags", "tools", "model")
_FIELD_DEFAULTS = {"title": "", "goal": "", "instructions": "", "tags": [], "tools": [], "model": ""}


def persist_playbook(data: dict, *, merge: bool) -> dict:
    """Write a playbook dict to the store, returning it as a plain dict.

    `merge=False` is today's behavior (main.py's HTTP form handlers): a full
    replace, with pydantic defaults for anything omitted. `merge=True` (used by
    the edit_playbook tool) instead keeps each field's *existing* value when the
    caller's dict doesn't supply it — a model calling with a partial argument
    set must not silently blank out the fields it didn't mention.

    `order` is never taken from `data` in merge mode: it is preserved from the
    existing playbook on update, or appended at the tail on create. Position 0
    is unconditionally the active system prompt (see get_main_playbook) — moving
    a playbook there is `make_main`'s job, never an accidental side effect of an
    ordinary field edit.
    """
    existing_id = str(data.get("id") or "")
    existing = playbook_store.get_playbook(existing_id) if existing_id else None

    if merge:
        fields = {}
        for key in _EDITABLE_FIELDS:
            if key in data and data[key] is not None:
                fields[key] = data[key]
            elif existing is not None:
                fields[key] = getattr(existing, key)
            else:
                fields[key] = _FIELD_DEFAULTS[key]
        if existing is None and not (fields["title"] and fields["goal"] and fields["instructions"]):
            raise ValueError("title, goal, and instructions are required to create a new playbook")
    else:
        fields = {key: data.get(key, _FIELD_DEFAULTS[key]) for key in _EDITABLE_FIELDS}

    order = existing.order if existing else data.get("order", len(playbook_store.all_playbooks()))
    item = PlaybookItem(id=existing_id or str(uuid4()), order=order, **fields)
    playbook_store.add_playbook(item)
    return item.model_dump()


def make_main(playbook_id: str) -> None:
    """Reorder so `playbook_id` is position 0 (the active system prompt)."""
    all_ids = [p.id for p in playbook_store.all_playbooks()]
    if playbook_id not in all_ids:
        raise ValueError(f"no playbook with id {playbook_id!r}")
    ordered = [playbook_id] + [pid for pid in all_ids if pid != playbook_id]
    playbook_store.reorder_playbooks(ordered)


class PlaybookManager:
    @classmethod
    def _all(cls) -> List[PlaybookItem]:
        """Return all playbooks sorted by order (position 0 is always main)."""
        return playbook_store.all_playbooks()

    @classmethod
    def get_main_playbook(cls) -> PlaybookItem | None:
        playbooks = cls._all()
        return playbooks[0] if playbooks else None

    @classmethod
    def get_context_playbooks(cls) -> List[PlaybookItem]:
        """All playbooks after the first — injected as reference context."""
        playbooks = cls._all()
        return playbooks[1:] if len(playbooks) > 1 else []

    @classmethod
    def get_system_prompt(cls) -> str:
        playbook = cls.get_main_playbook()
        if not playbook:
            return ""
        goal = (getattr(playbook, "goal", "") or "").strip()
        instructions = (getattr(playbook, "instructions", "") or "").strip()
        if goal and instructions:
            return f"{goal}\n\n{instructions}"
        return goal or instructions