"""Phase 2 — deterministically generate ~100 core skill packages (no LLM).

Selection: knowledge-work occupations (management, business, IT, HR, finance,
marketing, support) = curated ISCO-08 prefixes, restricted to ESCO occupations
that have an O*NET crosswalk match (so every package gets tasks + tools).
The recruiter ("recruitment consultant", O*NET 13-1071) is always included.

Output per occupation: skills/<slug>/SKILL.md + references/ + manifest.json
Everything is regenerated from MSSQL on each run (idempotent).
"""
import json
import os
import re
import sys
import unicodedata
from datetime import date

sys.path.insert(0, os.path.dirname(__file__))
from db import connect

SKILLS_DIR = os.path.join(os.path.dirname(__file__), "..", "skills")
TARGET_COUNT = 100

# ISCO-08 prefixes for knowledge work (management, business, IT, HR, finance,
# marketing, support). HR/recruiting lives in 242 (2423) and 333 (3333).
ISCO_PREFIXES = (
    "11", "12", "13",        # chief executives, managers
    "241", "242", "243",     # finance, admin/HR, sales & marketing professionals
    "251", "252",            # software/ICT professionals
    "261",                   # legal (contracts) — knowledge work
    "331", "332", "333", "334",  # business associate professionals
    "351", "352",            # ICT technicians/support
    "422",                   # client information workers (support)
)

MUST_INCLUDE_URIS = [
    # recruitment consultant (the deep-dive occupation of phase 3)
    "recruitment consultant",
]

ONET_ATTRIBUTION = (
    "This package includes information from the O*NET Database (v30.3) by the "
    "U.S. Department of Labor, Employment and Training Administration "
    "(USDOL/ETA), CC BY 4.0. skillfactor is not endorsed by USDOL/ETA. "
    "ESCO data (v1.2.1) (c) European Union, used per the ESCO download "
    "conditions: https://esco.ec.europa.eu/en/use-esco/download"
)


def slugify(label: str) -> str:
    s = unicodedata.normalize("NFKD", label).encode("ascii", "ignore").decode()
    s = re.sub(r"[^a-z0-9]+", "-", s.lower()).strip("-")
    return s[:80]


def q(cn, sql, *params):
    cur = cn.cursor()
    cur.execute(sql, *params)
    cols = [c[0] for c in cur.description]
    return [dict(zip(cols, row)) for row in cur.fetchall()]


def pick_occupations(cn):
    """Deterministic candidate list: ESCO occupation + best O*NET match."""
    rows = q(cn, """
        SELECT o.concept_uri, o.preferred_label, o.alt_labels, o.description,
               o.definition, o.isco_group, o.code,
               cw.onet_id, cw.onet_title, cw.match_type,
               ig.preferred_label AS isco_label
        FROM esco_occupation o
        JOIN crosswalk_esco_onet cw ON cw.esco_uri = o.concept_uri
        LEFT JOIN esco_isco_group ig ON ig.code = o.isco_group
        WHERE cw.match_type IN ('exactMatch','closeMatch')
    """)
    # best match per ESCO occupation: exactMatch wins, then lowest onet_id (stable)
    best = {}
    for r in rows:
        k = r["concept_uri"]
        rank = (0 if r["match_type"] == "exactMatch" else 1, r["onet_id"])
        if k not in best or rank < best[k][0]:
            best[k] = (rank, r)
    cands = [v[1] for v in best.values()]

    # bucket per ISCO prefix, then round-robin across buckets so the selection
    # spreads over management, business, IT, HR, finance, marketing and support
    # instead of filling up with ISCO 11-13 managers alphabetically
    buckets = {p: [] for p in ISCO_PREFIXES}
    for r in cands:
        grp = r["isco_group"] or ""
        for p in ISCO_PREFIXES:
            if grp.startswith(p):
                buckets[p].append(r)
                break
    for p in buckets:
        buckets[p].sort(key=lambda r: (r["isco_group"] or "", r["preferred_label"] or ""))

    # force-include the recruiter even if selection window would cut it
    selected, seen = [], set()
    for r in cands:
        if (r["preferred_label"] or "").lower() in MUST_INCLUDE_URIS:
            selected.append(r)
            seen.add(r["concept_uri"])
    while len(selected) < TARGET_COUNT and any(buckets.values()):
        for p in ISCO_PREFIXES:
            if len(selected) >= TARGET_COUNT:
                break
            while buckets[p]:
                r = buckets[p].pop(0)
                if r["concept_uri"] not in seen:
                    selected.append(r)
                    seen.add(r["concept_uri"])
                    break
    return selected


def esco_skills_for(cn, uri):
    return q(cn, """
        SELECT r.relation_type, r.skill_type, s.preferred_label, s.description
        FROM esco_occ_skill r
        JOIN esco_skill s ON s.concept_uri = r.skill_uri
        WHERE r.occupation_uri = ?
        ORDER BY CASE r.relation_type WHEN 'essential' THEN 0 ELSE 1 END,
                 s.preferred_label
    """, uri)


def onet_tasks_for(cn, soc):
    return q(cn, """
        SELECT task, task_type FROM onet_task WHERE soc_code = ?
        ORDER BY CASE task_type WHEN 'Core' THEN 0 ELSE 1 END, task_id
    """, soc)


def onet_dwas_for(cn, soc):
    return q(cn, """
        SELECT DISTINCT d.dwa_name
        FROM onet_task_dwa td JOIN onet_dwa d ON d.dwa_id = td.dwa_id
        WHERE td.soc_code = ? ORDER BY d.dwa_name
    """, soc)


def onet_software_for(cn, soc):
    return q(cn, """
        SELECT example, element_name, hot_technology
        FROM onet_software WHERE soc_code = ?
        ORDER BY CASE hot_technology WHEN 'Y' THEN 0 ELSE 1 END, element_name, example
    """, soc)


def alt_label_list(alt_labels):
    if not alt_labels:
        return []
    return [a.strip() for a in str(alt_labels).splitlines() if a.strip()]


def write(path, content):
    os.makedirs(os.path.dirname(path), exist_ok=True)
    with open(path, "w", encoding="utf-8", newline="\n") as f:
        f.write(content)


def gen_package(cn, r):
    label = r["preferred_label"]
    slug = slugify(label)
    base = os.path.join(SKILLS_DIR, slug)
    alts = alt_label_list(r["alt_labels"])
    skills = esco_skills_for(cn, r["concept_uri"])
    tasks = onet_tasks_for(cn, r["onet_id"])
    dwas = onet_dwas_for(cn, r["onet_id"])
    software = onet_software_for(cn, r["onet_id"])

    core_tasks = [t["task"] for t in tasks if t["task_type"] == "Core"][:8] or \
                 [t["task"] for t in tasks][:8]
    essential = [s for s in skills if s["relation_type"] == "essential"]
    optional = [s for s in skills if s["relation_type"] != "essential"]
    hot = [s for s in software if s["hot_technology"] == "Y"]

    trigger_examples = "; ".join(core_tasks[:3])
    description = (
        f"Occupational skill for the role '{label}'"
        + (f" (also: {', '.join(alts[:6])})" if alts else "")
        + f". Use when the user asks for typical {label} work such as: {trigger_examples}"
    )

    # --- SKILL.md (< 300 lines, progressive disclosure into references/) ---
    lines = [
        "---",
        f"name: {slug}",
        f"description: {json.dumps(description)}",
        "---",
        "",
        f"# {label.title()}",
        "",
        (r["description"] or r["definition"] or "").strip(),
        "",
        "## Core workflow",
        "",
    ]
    for i, t in enumerate(core_tasks, 1):
        lines.append(f"{i}. {t}")
    lines += [
        "",
        "## How to use this skill",
        "",
        "- Read `references/profile.md` for the occupation profile and scope.",
        "- Consult `references/tasks.md` for the full task and activity inventory.",
        "- Check `references/skills.md` for essential vs. optional competences.",
        "- Check `references/tools.md` for the software commonly used in this role.",
    ]
    # enrichment files (phase 3) survive regeneration — link them when present
    enrichment = [
        ("references/market.md", "FULL market-evidence report from real job ads (rankings, seniority, titles)"),
        ("references/usecases.md", "ranked work packages the agent can take over"),
        ("references/intake.md", "mandatory questions before starting any task"),
        ("references/quality.md", "acceptance criteria for deliverables"),
        ("references/glossary.md", "definitions of the key tools and methods"),
        ("references/literature.md", "standard works per top skill"),
        ("evals/", "test tasks with pass criteria"),
    ]
    for rel, why in enrichment:
        if os.path.exists(os.path.join(base, rel.rstrip("/"))):
            lines.append(f"- See `{rel}` — {why}.")
    lines += [
        "",
        "## Key competences (essential)",
        "",
    ]
    for s in essential[:15]:
        lines.append(f"- {s['preferred_label']}")
    if hot:
        lines += ["", "## Hot technologies", ""]
        seen = set()
        for s in hot:
            if s["example"] not in seen:
                lines.append(f"- {s['example']}")
                seen.add(s["example"])
            if len(seen) >= 10:
                break
    lines += [
        "",
        "---",
        f"*Sources: ESCO v1.2.1 ({r['concept_uri']}), O*NET 30.3 ({r['onet_id']}). "
        "See manifest.json for licensing/attribution.*",
        "",
    ]
    write(os.path.join(base, "SKILL.md"), "\n".join(lines))

    # --- references/profile.md ---
    prof = [
        f"# Occupation profile — {label}",
        "",
        f"- **ESCO URI:** {r['concept_uri']}",
        f"- **ESCO code:** {r['code']}",
        f"- **ISCO-08 group:** {r['isco_group']} — {r['isco_label']}",
        f"- **O*NET-SOC:** {r['onet_id']} — {r['onet_title']} (match: {r['match_type']})",
        "",
        "## Description (ESCO)",
        "",
        (r["description"] or "").strip(),
    ]
    if r["definition"]:
        prof += ["", "## Definition", "", str(r["definition"]).strip()]
    if alts:
        prof += ["", "## Alternative labels", ""] + [f"- {a}" for a in alts]
    write(os.path.join(base, "references", "profile.md"), "\n".join(prof) + "\n")

    # --- references/tasks.md ---
    tl = [f"# Tasks & work activities — {label}", "",
          f"Source: O*NET 30.3, occupation {r['onet_id']} ({r['onet_title']}).", "",
          "## Task statements", ""]
    for t in tasks:
        tl.append(f"- **[{t['task_type'] or 'n/a'}]** {t['task']}")
    if dwas:
        tl += ["", "## Detailed work activities", ""]
        tl += [f"- {d['dwa_name']}" for d in dwas]
    write(os.path.join(base, "references", "tasks.md"), "\n".join(tl) + "\n")

    # --- references/skills.md ---
    sl = [f"# Competences — {label}", "",
          f"Source: ESCO v1.2.1 occupation-skill relations ({r['concept_uri']}).", "",
          "## Essential", ""]
    for s in essential:
        sl.append(f"- **{s['preferred_label']}** ({s['skill_type']})")
    sl += ["", "## Optional", ""]
    for s in optional:
        sl.append(f"- {s['preferred_label']} ({s['skill_type']})")
    write(os.path.join(base, "references", "skills.md"), "\n".join(sl) + "\n")

    # --- references/tools.md ---
    ol = [f"# Tools & technology — {label}", "",
          f"Source: O*NET 30.3 'Software Skills' for {r['onet_id']}.",
          "", "| Software | Category | Hot technology |", "|---|---|---|"]
    for s in software:
        ol.append(f"| {s['example']} | {s['element_name']} | {'yes' if s['hot_technology'] == 'Y' else ''} |")
    write(os.path.join(base, "references", "tools.md"), "\n".join(ol) + "\n")

    # --- manifest.json ---
    manifest = {
        "name": slug,
        "title": label,
        "version": "0.1.0",
        "layer": "core",
        "language": "en",
        "generated": date.today().isoformat(),
        "ids": {
            "esco_uri": r["concept_uri"],
            "esco_code": r["code"],
            "isco_group": r["isco_group"],
            "onet_soc": r["onet_id"],
            "crosswalk_match": r["match_type"],
        },
        "sources": [
            {"name": "ESCO", "version": "1.2.1", "url": "https://esco.ec.europa.eu/"},
            {"name": "O*NET", "version": "30.3", "url": "https://www.onetcenter.org/", "license": "CC BY 4.0"},
        ],
        "attribution": ONET_ATTRIBUTION,
        "counts": {
            "tasks": len(tasks),
            "dwas": len(dwas),
            "skills_essential": len(essential),
            "skills_optional": len(optional),
            "software": len(software),
        },
    }
    write(os.path.join(base, "manifest.json"), json.dumps(manifest, indent=2) + "\n")
    return slug


def main():
    cn = connect()
    selected = pick_occupations(cn)
    print(f"Selected {len(selected)} occupations")
    slugs = []
    for r in selected:
        slugs.append(gen_package(cn, r))
    cn.close()
    # remove packages from a previous selection that are no longer chosen
    import shutil
    for d in os.listdir(SKILLS_DIR):
        full = os.path.join(SKILLS_DIR, d)
        if os.path.isdir(full) and d not in slugs:
            shutil.rmtree(full)
            print(f"removed stale package: {d}")
    print(f"Generated {len(slugs)} packages under skills/")
    print("Recruiter included:", any("recruit" in s for s in slugs))


if __name__ == "__main__":
    main()
