"""Resume + budget bookkeeping for the full-catalog batch runs.

Single source of truth: data/progress.json
{
  "jsearch_requests_total": 123,          # lifetime counter, hard cap below
  "occupations": {
    "<slug>": {
      "package":   "done|pending",        # phase 1 (deterministic generator)
      "evidence":  "done|fetching|extracting|aggregating|failed|pending",
      "depth":     "done|in_progress|failed|pending",   # phases 2b/3
      "tier":      1|2|3,                 # 1 = top-200, 3 = night batch rest
      "requests":  7,                     # JSearch requests spent on this slug
      "ads":       61,                    # unique ads with full text
      "error":     "last error message"   # only present after a failure
    }, ...
  }
}

Writes are atomic (tmp + os.replace) so an aborted run never corrupts the
file. Every JSearch request MUST go through spend_request() — it enforces
the hard budget. Cache hits cost nothing and are not counted.
"""
import json
import os

BASE = os.path.join(os.path.dirname(__file__), "..")
PROGRESS = os.path.join(BASE, "data", "progress.json")

JSEARCH_BUDGET_TOTAL = 33000  # hard cap per finalize order (skillfactor_finalize.md)


class BudgetExhausted(RuntimeError):
    pass


def load():
    if os.path.exists(PROGRESS):
        with open(PROGRESS, encoding="utf-8") as f:
            return json.load(f)
    return {"jsearch_requests_total": 0, "occupations": {}}


def save(state):
    tmp = PROGRESS + ".tmp"
    with open(tmp, "w", encoding="utf-8") as f:
        json.dump(state, f, ensure_ascii=False, indent=1)
    os.replace(tmp, PROGRESS)


def occ(state, slug):
    return state["occupations"].setdefault(slug, {
        "package": "pending", "evidence": "pending", "depth": "pending",
        "tier": 3, "requests": 0, "ads": 0,
    })


def spend_request(state, slug, n=1):
    """Count n JSearch requests against the global and per-slug budget.
    Call BEFORE the HTTP request; raises BudgetExhausted at the cap."""
    if state["jsearch_requests_total"] + n > JSEARCH_BUDGET_TOTAL:
        raise BudgetExhausted(
            f"JSearch budget {JSEARCH_BUDGET_TOTAL} reached "
            f"({state['jsearch_requests_total']} spent)")
    state["jsearch_requests_total"] += n
    occ(state, slug)["requests"] += n
    save(state)
