// scripts/seed-backlinks.ts
// Usage: bun scripts/seed-backlinks.ts "<path-to.tsv>"
// Emits src/lib/backlink/fixtures.ts and logs merged duplicates.
import { readFileSync, writeFileSync } from "node:fs";
import * as wasm from "import-core";
import {
  dedupeMerge,
  domainNorm,
  normalizeUrl,
} from "../src/lib/backlink/normalize";
import {
  type Effort,
  KIND_FROM_CATEGORY,
  type LinkSourceSeed,
  type LinkType,
} from "../src/lib/backlink/types";

const tsvPath = process.argv[2];
if (!tsvPath) throw new Error("Pass the TSV path as argv[2]");

const EFFORT: Record<string, Effort> = {
  Low: "low",
  Medium: "medium",
  High: "high",
};
const LINKTYPE: Record<string, LinkType> = {
  "Do-Follow": "dofollow",
  "No-Follow": "nofollow",
  Mixed: "mixed",
};

/** Hosts where the meaningful unit is the path (subreddit / server / group), not the domain. */
const MULTI_TENANT = new Set([
  "reddit.com",
  "discord.com",
  "discord.gg",
  "linkedin.com",
  "t.me",
  "facebook.com",
]);

/** Dedup key: domain for normal hosts; domain + lowercased path for multi-tenant hosts. */
function dedupKey(url: string, dn: string): string {
  if (!MULTI_TENANT.has(dn)) return dn;
  try {
    const path = new URL(url).pathname.replace(/\/+$/, "").toLowerCase();
    return dn + path;
  } catch {
    return dn;
  }
}

// Canonical slugify — same import-core/wasm entry point as the reservoir CSV
// import path (src/lib/backlink/import-actions.ts). Verified byte-identical
// to the old local implementation over the full fixtures.ts dedupKey space
// plus unicode/edge-case inputs before this swap (0 mismatches).
const slugify = wasm.slugify as (s: string) => string;

const lines = readFileSync(tsvPath, "utf8").split("\n");
const raw: LinkSourceSeed[] = [];

for (const line of lines) {
  const c = line.split("\t");
  // TSV has an empty leading column: [ "", Platform, URL, Category, DA, Effort, LinkType, Status, Link, Project ]
  const platform = (c[1] ?? "").trim();
  const urlCell = (c[2] ?? "").trim();
  const category = (c[3] ?? "").trim();
  const daCell = (c[4] ?? "").trim();
  const effortCell = (c[5] ?? "").trim();
  const linkTypeCell = (c[6] ?? "").trim();

  // skip header/junk/empty rows
  if (!platform || platform === "Platform") continue;
  if (platform.includes("outrank.so") || category.startsWith("→")) continue;

  const url = normalizeUrl(urlCell);
  const dn = domainNorm(urlCell);
  if (!url || !dn) continue;

  const da =
    daCell === "" || daCell === "—" || Number.isNaN(Number(daCell))
      ? null
      : Number(daCell);

  raw.push({
    id: slugify(dedupKey(url, dn)),
    platform,
    url,
    domainNorm: dn,
    kind: KIND_FROM_CATEGORY[category] ?? "other",
    da,
    dr: null,
    effort: EFFORT[effortCell] ?? "medium",
    linkType: LINKTYPE[linkTypeCell] ?? "nofollow",
    language: "en",
    themes: [],
    tags: [],
    flags: { toxic: false, disavow: false },
  });
}

const { sources, merges } = dedupeMerge(raw, (s) =>
  dedupKey(s.url, s.domainNorm),
);

// ensure unique ids after dedup (distinct domains can slugify to the same id)
const usedIds = new Set<string>();
for (const s of sources) {
  const base = s.id;
  let id = base;
  let n = 2;
  while (usedIds.has(id)) {
    id = `${base}-${n}`;
    n += 1;
  }
  s.id = id;
  usedIds.add(id);
}

// stable order: by platform name
sources.sort((a, b) => a.platform.localeCompare(b.platform, "en"));

const header =
  "// src/lib/backlink/fixtures.ts\n" +
  "// GENERATED by scripts/seed-backlinks.ts — do not hand-edit.\n" +
  'import type { LinkSourceSeed } from "./types";\n\n' +
  "export const BACKLINK_SOURCES: LinkSourceSeed[] = ";
writeFileSync(
  "src/lib/backlink/fixtures.ts",
  `${header}${JSON.stringify(sources, null, 2)};\n`,
  "utf8",
);

console.log(`Wrote ${sources.length} sources (from ${raw.length} rows).`);
console.log(`Merged ${merges.length} duplicate domains:`);
for (const m of merges) {
  console.log(`  ${m.domainNorm}: kept "${m.kept}", dropped "${m.dropped}"`);
}
