// cli/src/ingest-shengkao.ts
// 把各省"省考/事业单位/消防"退役专项岗位（标准化CSV）追加进已有 positions-index.json.gz。
//
// 为什么是"追加"而非"全量重建"：国考的原始职位表(cli/raw/*.csv)未纳入版本库，
// 无法从零重建完整索引；省考退役专项又是聚焦子集（只收退役/基层项目定向岗，
// 不收某省全部岗位）。故这里加载现有索引、删除旧的 shengkao 记录、再追加最新省考记录。
//
// 标准化CSV列（各省调研agent统一产出，放在 cli/raw/shengkao/<省>.csv）：
//   source_province,year,position_code,dept_name,bureau,position_name,position_desc,
//   headcount,education,degree,major,political,work_location,remarks,service_year,channel,source_url
//
// Usage: npx tsx src/ingest-shengkao.ts            （读 cli/raw/shengkao/ 下全部csv）
//        npx tsx src/ingest-shengkao.ts <dir>      （指定目录）
import { readFileSync, writeFileSync, readdirSync, existsSync } from "node:fs";
import { gzipSync, gunzipSync } from "node:zlib";
import { join, dirname, basename } from "node:path";
import { fileURLToPath } from "node:url";
import { classifyPrograms } from "./codes.js";
const __dirname = dirname(fileURLToPath(import.meta.url));
// RFC4180 行解析：支持引号字段内含逗号/换行/转义双引号
function parseCSV(content) {
    const rows = [];
    let cur = [];
    let field = "";
    let q = false;
    for (let i = 0; i < content.length; i++) {
        const ch = content[i];
        if (q) {
            if (ch === '"' && content[i + 1] === '"') {
                field += '"';
                i++;
            }
            else if (ch === '"') {
                q = false;
            }
            else
                field += ch;
        }
        else {
            if (ch === '"')
                q = true;
            else if (ch === ",") {
                cur.push(field);
                field = "";
            }
            else if (ch === "\n" || ch === "\r") {
                if (ch === "\r" && content[i + 1] === "\n")
                    i++;
                cur.push(field);
                if (cur.some((c) => c !== ""))
                    rows.push(cur);
                cur = [];
                field = "";
            }
            else
                field += ch;
        }
    }
    if (field !== "" || cur.length) {
        cur.push(field);
        if (cur.some((c) => c !== ""))
            rows.push(cur);
    }
    return rows;
}
// 各省退役专项在备注里的实际措辞拼接，用于复用 classifyPrograms 识别标签
function programText(p) {
    return [p.position_name, p.position_desc, p.major, p.remarks, p.dept_name, p.bureau].filter(Boolean).join(" ");
}
// 招录渠道 → 顶层考试类型。省考/定向乡镇归 shengkao；消防员、事业单位各自独立。
function channelToExam(channel) {
    if (channel.includes("消防"))
        return "xiaofang";
    if (channel.includes("事业"))
        return "shiye";
    return "shengkao";
}
const LOCAL_EXAMS = new Set(["shengkao", "xiaofang", "shiye"]);
function rowsToPositions(csvPath) {
    const rows = parseCSV(readFileSync(csvPath, "utf-8"));
    if (rows.length < 2)
        return [];
    const hdr = rows[0].map((h) => h.trim());
    const ix = {};
    hdr.forEach((h, i) => (ix[h] = i));
    const get = (row, col) => (ix[col] != null ? (row[ix[col]] ?? "").trim() : "");
    const out = [];
    for (let r = 1; r < rows.length; r++) {
        const row = rows[r];
        const code = get(row, "position_code");
        if (!code && !get(row, "position_name"))
            continue;
        const channel = get(row, "channel") || "省考";
        const pos = {
            id: code || `${get(row, "source_province")}-${r}`,
            year: Number(get(row, "year")) || new Date().getFullYear(),
            exam: channelToExam(channel),
            dept_code: "",
            dept_name: get(row, "dept_name"),
            bureau: get(row, "bureau"),
            inst_type: "",
            inst_level: "",
            position_name: get(row, "position_name"),
            position_attr: "",
            position_dist: "",
            position_desc: get(row, "position_desc"),
            exam_category: channel,
            headcount: Number(get(row, "headcount")) || 1,
            education: get(row, "education"),
            degree: get(row, "degree"),
            major: get(row, "major") || "不限",
            political: get(row, "political") || "不限",
            grassroots_years: "无限制",
            work_location: get(row, "work_location") || get(row, "source_province"),
            remarks: get(row, "remarks"),
            programs: [],
            province: get(row, "source_province"),
            channel,
            service_year: get(row, "service_year"),
        };
        // 各省退役措辞极不统一（退役士兵/退役军人/退役军士/安排工作的退役/退役安置专岗/
        // 专武干部+服役满5年 等），窄正则会漏标。这些CSV是调研agent已筛选、审核agent已逐行
        // 核验过的"退役可报"清单，故统一保证"退役士兵"标签；再用 classifyPrograms 补"基层项目"
        // 标签（合并定向/共享岗会同时带两个标签，与国考口径一致）。
        const tags = classifyPrograms(programText(pos));
        if (!tags.includes("退役士兵"))
            tags.unshift("退役士兵");
        pos.programs = tags;
        out.push(pos);
    }
    return out;
}
function main() {
    const dir = process.argv[2] ?? join(__dirname, "..", "raw", "shengkao");
    const idxPath = join(__dirname, "..", "data", "positions-index.json.gz");
    if (!existsSync(dir)) {
        console.error(`目录不存在: ${dir}`);
        process.exit(1);
    }
    if (!existsSync(idxPath)) {
        console.error(`索引不存在: ${idxPath}`);
        process.exit(1);
    }
    const idx = JSON.parse(gunzipSync(readFileSync(idxPath)).toString());
    const before = idx.positions.length;
    // 删除旧的地方退役专项记录(省考/消防/事业单位)，保证可重复运行
    idx.positions = idx.positions.filter((p) => !LOCAL_EXAMS.has(p.exam));
    const afterStrip = idx.positions.length;
    const files = readdirSync(dir).filter((f) => f.endsWith(".csv")).sort();
    let added = 0;
    const perProvince = {};
    for (const f of files) {
        const positions = rowsToPositions(join(dir, f));
        idx.positions.push(...positions);
        added += positions.length;
        const prov = positions[0]?.province ?? basename(f, ".csv");
        perProvince[prov] = (perProvince[prov] ?? 0) + positions.length;
    }
    const years = new Set(idx.positions.map((p) => p.year));
    const exams = new Set(idx.positions.map((p) => p.exam));
    idx.meta.version = "0.6.0";
    idx.meta.built_at = new Date().toISOString();
    idx.meta.exams = [...exams].sort();
    idx.meta.years = [...years].sort();
    idx.meta.total_positions = idx.positions.length;
    writeFileSync(idxPath, gzipSync(JSON.stringify(idx)));
    const local = idx.positions.filter((p) => LOCAL_EXAMS.has(p.exam));
    const byChannel = {};
    for (const p of local)
        byChannel[p.channel ?? "?"] = (byChannel[p.channel ?? "?"] ?? 0) + 1;
    console.log(`原索引 ${before} 条；删除旧地方专项后 ${afterStrip} 条；本次追加 ${added} 条（来自 ${files.length} 个CSV）`);
    console.log(`各省/源追加数:`, JSON.stringify(perProvince));
    console.log(`按渠道:`, JSON.stringify(byChannel));
    console.log(`带"退役士兵"标签: ${local.filter((p) => p.programs.includes("退役士兵")).length} 条`);
    console.log(`新索引总数: ${idx.positions.length}；meta.version=${idx.meta.version}；exams=${idx.meta.exams.join(",")}`);
}
main();
