#!/usr/bin/env node
import process from "node:process";

function getEnv(name, fallback = "") {
  return String(process.env[name] ?? fallback).trim();
}

function sh(value) {
  return JSON.stringify(String(value ?? ""));
}

function escapeRegExp(s) {
  return String(s).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}

function pickLine(body, key) {
  const re = new RegExp(`^\\s*${escapeRegExp(key)}\\s*:\\s*([^\\n\\r]+)`, "mi");
  const m = String(body || "").match(re);
  return m ? m[1].trim() : "";
}

function pickHeadingValue(body, headingKey) {
  const re = new RegExp(
    `^##\\s*${escapeRegExp(headingKey)}[^\\n]*\\n([\\s\\S]*?)(?=\\n##\\s|\\n\\s*$)`,
    "mi"
  );
  const m = String(body || "").match(re);
  if (!m) return "";
  const lines = m[1].split(/\r?\n/).map((l) => l.trim());
  for (const l of lines) {
    if (!l) continue;
    if (l.startsWith("<!--")) continue;
    return l.replace(/^\/?/, "/").trim();
  }
  return "";
}

function normalizeChemin(chemin) {
  let c = String(chemin || "").trim();
  if (!c) return "";
  if (!c.startsWith("/")) c = "/" + c;
  if (!c.endsWith("/")) c += "/";
  return c;
}

function extractCheminFromAnyUrl(text) {
  const s = String(text || "");
  const m = s.match(/(\/[a-z0-9\-]+\/[a-z0-9\-\/]+\/)#p-\d+-[0-9a-f]{8}/i);
  return m ? m[1] : "";
}

function inferType(issue) {
  const title = String(issue?.title || "");
  const body = String(issue?.body || "").replace(/\r\n/g, "\n");
  const fromBody = String(pickLine(body, "Type") || "").trim().toLowerCase();
  if (fromBody) return fromBody;

  if (title.startsWith("[Correction]")) return "type/correction";
  if (title.startsWith("[Fact-check]") || title.startsWith("[Vérification]")) return "type/fact-check";
  return "";
}

function inferChemin(issue) {
  const title = String(issue?.title || "");
  const body = String(issue?.body || "").replace(/\r\n/g, "\n");

  return normalizeChemin(
    pickLine(body, "Chemin") ||
      pickHeadingValue(body, "Chemin") ||
      extractCheminFromAnyUrl(body) ||
      extractCheminFromAnyUrl(title)
  );
}

function labelsOf(issue) {
  return Array.isArray(issue?.labels)
    ? issue.labels.map((l) => String(l?.name || "")).filter(Boolean)
    : [];
}

function issueNumber(issue) {
  return Number(issue?.number || issue?.index || 0);
}

function parseMeta(issue) {
  const labels = labelsOf(issue);
  const type = inferType(issue);
  const chemin = inferChemin(issue);
  const number = issueNumber(issue);

  const hasApproved = labels.includes("state/approved");
  const hasRejected = labels.includes("state/rejected");
  const isProposer = type === "type/correction" || type === "type/fact-check";
  const isOpen = String(issue?.state || "open") === "open";
  const isPR = Boolean(issue?.pull_request);

  const eligible =
    number > 0 &&
    isOpen &&
    !isPR &&
    hasApproved &&
    !hasRejected &&
    isProposer &&
    Boolean(chemin);

  return {
    issue,
    number,
    type,
    chemin,
    labels,
    hasApproved,
    hasRejected,
    eligible,
  };
}

async function fetchJson(url, token) {
  const res = await fetch(url, {
    headers: {
      Authorization: `token ${token}`,
      Accept: "application/json",
      "User-Agent": "archicratie-pick-proposer-issue/1.0",
    },
  });
  if (!res.ok) {
    const t = await res.text().catch(() => "");
    throw new Error(`HTTP ${res.status} ${url}\n${t}`);
  }
  return await res.json();
}

async function fetchIssue(apiBase, owner, repo, token, n) {
  const url = `${apiBase}/api/v1/repos/${owner}/${repo}/issues/${n}`;
  return await fetchJson(url, token);
}

async function listOpenIssues(apiBase, owner, repo, token) {
  const out = [];
  let page = 1;
  const limit = 100;

  while (true) {
    const url = `${apiBase}/api/v1/repos/${owner}/${repo}/issues?state=open&page=${page}&limit=${limit}`;
    const batch = await fetchJson(url, token);
    if (!Array.isArray(batch) || batch.length === 0) break;
    out.push(...batch);
    if (batch.length < limit) break;
    page += 1;
  }

  return out;
}

function emitNone(reason) {
  process.stdout.write(
    [
      `TARGET_FOUND="0"`,
      `TARGET_REASON=${sh(reason)}`,
      `TARGET_PRIMARY_ISSUE=""`,
      `TARGET_ISSUES=""`,
      `TARGET_COUNT="0"`,
      `TARGET_CHEMIN=""`,
    ].join("\n") + "\n"
  );
}

async function main() {
  const token = getEnv("FORGE_TOKEN");
  const owner = getEnv("GITEA_OWNER");
  const repo = getEnv("GITEA_REPO");
  const apiBase = (getEnv("FORGE_API") || getEnv("FORGE_BASE")).replace(/\/+$/, "");
  const explicit = Number(process.argv[2] || 0);

  if (!token) throw new Error("Missing FORGE_TOKEN");
  if (!owner || !repo) throw new Error("Missing GITEA_OWNER / GITEA_REPO");
  if (!apiBase) throw new Error("Missing FORGE_API / FORGE_BASE");

  let metas = [];

  if (explicit > 0) {
    const issue = await fetchIssue(apiBase, owner, repo, token, explicit);
    const meta = parseMeta(issue);

    if (!meta.eligible) {
      emitNone(
        !meta.hasApproved
          ? "explicit_issue_not_approved"
          : meta.hasRejected
            ? "explicit_issue_rejected"
            : !meta.type
              ? "explicit_issue_missing_type"
              : !meta.chemin
                ? "explicit_issue_missing_chemin"
                : "explicit_issue_not_eligible"
      );
      return;
    }

    const openIssues = await listOpenIssues(apiBase, owner, repo, token);
    metas = openIssues.map(parseMeta).filter((m) => m.eligible && m.chemin === meta.chemin);
  } else {
    const openIssues = await listOpenIssues(apiBase, owner, repo, token);
    metas = openIssues.map(parseMeta).filter((m) => m.eligible);

    if (metas.length === 0) {
      emitNone("no_open_approved_proposer_issue");
      return;
    }

    metas.sort((a, b) => a.number - b.number);
    const first = metas[0];
    metas = metas.filter((m) => m.chemin === first.chemin);
  }

  metas.sort((a, b) => a.number - b.number);

  if (metas.length === 0) {
    emitNone("no_batch_for_path");
    return;
  }

  const primary = metas[0];
  const issues = metas.map((m) => String(m.number));

  process.stdout.write(
    [
      `TARGET_FOUND="1"`,
      `TARGET_REASON="ok"`,
      `TARGET_PRIMARY_ISSUE=${sh(primary.number)}`,
      `TARGET_ISSUES=${sh(issues.join(" "))}`,
      `TARGET_COUNT=${sh(issues.length)}`,
      `TARGET_CHEMIN=${sh(primary.chemin)}`,
    ].join("\n") + "\n"
  );
}

main().catch((e) => {
  console.error("💥 pick-proposer-issue:", e?.message || e);
  process.exit(1);
});