"""In-memory job store with on-disk working directories.

Each upload creates a job with a uuid id and a working directory under
``data/jobs/<id>/``. Status and progress are tracked in an in-memory dict; long
running steps run in background threads that update ``progress`` (0..1).
"""
from __future__ import annotations

import threading
import uuid
from dataclasses import dataclass, field
from pathlib import Path
from typing import Optional

# Status flow: uploaded -> stabilizing -> stabilized -> generating -> ready
# (plus "error" if a background step raises).
DATA_ROOT = Path(__file__).resolve().parent.parent / "data" / "jobs"


@dataclass
class Job:
    id: str
    dir: Path
    status: str = "uploaded"
    progress: float = 0.0
    meta: dict = field(default_factory=dict)
    error: Optional[str] = None
    # Populated after stabilization / generation:
    frame_count: int = 0
    result: dict = field(default_factory=dict)

    @property
    def input_path(self) -> Path:
        return self.dir / "input.mp4"

    @property
    def aligned_dir(self) -> Path:
        return self.dir / "aligned"

    @property
    def ply_path(self) -> Path:
        return self.dir / "output.ply"

    @property
    def stl_path(self) -> Path:
        return self.dir / "output.stl"

    def to_status(self) -> dict:
        return {
            "job_id": self.id,
            "status": self.status,
            "progress": round(self.progress, 4),
            "frame_count": self.frame_count,
            "meta": self.meta,
            "error": self.error,
            "result": self.result,
        }


class JobStore:
    def __init__(self, root: Path = DATA_ROOT):
        self.root = root
        self.root.mkdir(parents=True, exist_ok=True)
        self._jobs: dict[str, Job] = {}
        self._lock = threading.Lock()

    def create(self) -> Job:
        job_id = uuid.uuid4().hex
        job_dir = self.root / job_id
        job_dir.mkdir(parents=True, exist_ok=True)
        job = Job(id=job_id, dir=job_dir)
        with self._lock:
            self._jobs[job_id] = job
        return job

    def get(self, job_id: str) -> Optional[Job]:
        with self._lock:
            return self._jobs.get(job_id)

    def require(self, job_id: str) -> Job:
        job = self.get(job_id)
        if job is None:
            raise KeyError(job_id)
        return job

    def set_status(self, job: Job, status: str, progress: Optional[float] = None) -> None:
        with self._lock:
            job.status = status
            if progress is not None:
                job.progress = progress

    def set_progress(self, job: Job, progress: float) -> None:
        with self._lock:
            job.progress = progress

    def set_error(self, job: Job, message: str) -> None:
        with self._lock:
            job.status = "error"
            job.error = message
