"""model — what the Foundry catches.

A `Catch` is one downloaded source (a YouTube/track audio) plus, once
separated, its stems. It is persisted as `catch.json` in the catch's own
workspace folder, so the on-disk layout *is* the database:

    workspace/<slug>/
      source.<ext>        # the downloaded audio (engine1: yt-dlp bestaudio)
      source.info.json     # raw yt-dlp metadata sidecar (kept for provenance)
      stems/{drums,bass,other,vocals}.wav
      catch.json           # this model, serialized

Naming follows the project rule: metadata carries provenance. `source_url` /
`source_id` are the canonical pointers back to where a sample came from — never
guess them, they come from yt-dlp.
"""
from __future__ import annotations

import json
import re
from pathlib import Path
from typing import Optional

from pydantic import BaseModel, Field

# Demucs (and the Roformer 4-stem configs) emit exactly these, in this order.
STEMS = ("drums", "bass", "other", "vocals")

CATCH_FILE = "catch.json"


def slugify(text: str) -> str:
    """A filesystem- and Tidal-friendly slug. Lowercase, ascii-ish, `_`-joined."""
    text = text.strip().lower()
    text = re.sub(r"[^a-z0-9]+", "_", text)
    return text.strip("_") or "untitled"


class Stem(BaseModel):
    name: str  # one of STEMS
    path: str  # relative to the catch dir, e.g. "stems/bass.wav"


class Catch(BaseModel):
    """One source + its separation result."""

    slug: str
    title: str
    source_url: Optional[str] = None
    source_id: Optional[str] = None  # e.g. the YouTube id
    duration: Optional[float] = None  # seconds
    source_path: str  # relative to the catch dir, e.g. "source.webm"

    # Set once separated. `backend`/`model` record HOW the stems were made
    # (engine1 = demucs/htdemucs; engine2 = roformer/<ckpt>) — provenance again.
    backend: Optional[str] = None
    model: Optional[str] = None
    stems: list[Stem] = Field(default_factory=list)

    created: Optional[str] = None  # ISO timestamp, stamped by fetch

    # ── persistence ────────────────────────────────────────────────────────
    @property
    def separated(self) -> bool:
        return bool(self.stems)

    def dir(self, workspace: Path) -> Path:
        return Path(workspace) / self.slug

    def save(self, workspace: Path) -> Path:
        d = self.dir(workspace)
        d.mkdir(parents=True, exist_ok=True)
        out = d / CATCH_FILE
        out.write_text(json.dumps(self.model_dump(), indent=2))
        return out

    @classmethod
    def load(cls, catch_dir: Path) -> "Catch":
        return cls.model_validate_json((Path(catch_dir) / CATCH_FILE).read_text())
