# Copyright (c) 2025 Patrick Motsch
"""
Workflow entry points (Starts) — configuration outside the flow editor.

Kinds align with run envelope trigger.type where applicable.
"""

import uuid
from typing import Any, Dict, List, Optional

# On-demand (gear: Manueller Trigger, Formular)
KINDS_ON_DEMAND = frozenset({"manual", "form", "api"})

# Always-on (gear: Zeitplan, Immer aktiv, plus legacy listener kinds)
KINDS_ALWAYS_ON = frozenset({"schedule", "always_on", "email", "webhook", "event"})

ALL_KINDS = KINDS_ON_DEMAND | KINDS_ALWAYS_ON


def category_for_kind(kind: str) -> str:
    if kind in KINDS_ALWAYS_ON:
        return "always_on"
    return "on_demand"


def default_manual_entry_point() -> Dict[str, Any]:
    """Single default manual start when a workflow has no invocations yet."""
    return {
        "id": str(uuid.uuid4()),
        "kind": "manual",
        "category": "on_demand",
        "enabled": True,
        "title": "Jetzt ausführen",
        "description": {},
        "config": {},
    }


def _normalize_title(title: Any) -> str:
    """Extract a plain string from a title value for storage (not display)."""
    if isinstance(title, dict):
        picked = title.get("xx") or next((v for v in title.values() if v), None)
        return str(picked).strip() if picked else "Start"
    if isinstance(title, str) and title.strip():
        return title.strip()
    return "Start"


def normalize_invocation_entry(raw: Dict[str, Any]) -> Dict[str, Any]:
    """Validate and normalize a single entry point dict."""
    kind = (raw.get("kind") or "manual").strip()
    if kind not in ALL_KINDS:
        kind = "manual"
    cat = raw.get("category")
    if cat not in ("on_demand", "always_on"):
        cat = category_for_kind(kind)
    eid = raw.get("id") or str(uuid.uuid4())
    enabled = raw.get("enabled", True)
    if not isinstance(enabled, bool):
        enabled = bool(enabled)
    config = raw.get("config") if isinstance(raw.get("config"), dict) else {}
    desc = raw.get("description") if isinstance(raw.get("description"), dict) else {}
    return {
        "id": str(eid),
        "kind": kind,
        "category": cat,
        "enabled": enabled,
        "title": _normalize_title(raw.get("title")),
        "description": desc,
        "config": config,
    }


def normalize_invocations_list(items: Optional[List[Any]]) -> List[Dict[str, Any]]:
    if not items:
        return [default_manual_entry_point()]
    out: List[Dict[str, Any]] = []
    for raw in items:
        if isinstance(raw, dict):
            out.append(normalize_invocation_entry(raw))
    if not out:
        return [default_manual_entry_point()]
    return out


# Schedule / cron: wire an external job runner (APScheduler, Celery, system cron) to call
# POST .../execute with entryPointId set to a schedule entry — no separate in-process scheduler here yet.


def find_invocation(workflow: Dict[str, Any], entry_point_id: str) -> Optional[Dict[str, Any]]:
    for inv in workflow.get("invocations") or []:
        if isinstance(inv, dict) and inv.get("id") == entry_point_id:
            return inv
    return None
