#!/usr/bin/env node
/**
 * @agent-manifest
 * name:        seo-review
 * title:       SEO Reviewer
 * class:       content
 * trigger:     manual <slug> (auto-invoked by writer/approve gates)
 * description: Audit one article for EEAT / spam-policy / on-page SEO / AEO / readability → seo-review.json.
 * model:       reviewer=claude-opus-4-8
 * prompt:      prompts/seo-review.system.md
 * skills:      -
 * tools:       lib/claude.mjs
 * reads:       -
 * writes:      -
 * created:     2026-07-04
 * @end
 */
// SEO Specialist agent: audit one article for EEAT / spam-policy / on-page SEO / AEO / readability.
// Writes <postDir>/seo-review.json. Usage:
//   node scripts/seo-review.mjs <slug> [--published]
//     (default: review the draft in agents/drafts/<slug>; --published reviews the
//      live post in app/src/content/blog/<slug>)
import { existsSync, readFileSync } from "node:fs";
import { dirname, resolve, join } from "node:path";
import { fileURLToPath } from "node:url";
import { runClaude } from "./lib/claude.mjs";
import { loadJson, readFrontmatter, fmString } from "./lib/util.mjs";
import { readSlugs } from "./lib/blogData.mjs";

const HERE = dirname(fileURLToPath(import.meta.url));
const PIPELINE = resolve(HERE, "..");
const cfg = loadJson(join(PIPELINE, "config.json"));
const root = cfg.paths.projectRoot;

function postDirFor(slug, published) {
  const draftDir = join(root, cfg.paths.draftsDir, slug);
  const liveDir = join(root, cfg.paths.blogContentDir, slug);
  if (published) return liveDir;
  if (existsSync(join(draftDir, "index.mdx"))) return draftDir;
  if (existsSync(join(liveDir, "index.mdx"))) return liveDir;
  return draftDir; // will fail the existence check below
}

function primaryKeywordFor(slug, mdxPath) {
  const calPath = join(root, cfg.paths.calendar);
  if (existsSync(calPath)) {
    const entry = loadJson(calPath).find((e) => e.slug === slug);
    if (entry?.primaryKeyword) return entry.primaryKeyword;
  }
  if (existsSync(mdxPath)) {
    const t = fmString(readFrontmatter(readFileSync(mdxPath, "utf8")), "title");
    if (t) return t;
  }
  return slug.replace(/-/g, " ");
}

/** Run the SEO audit for one post. Returns the parsed audit object, or null on failure. */
export async function runSeoReview(slug, { published = false } = {}) {
  const postDir = postDirFor(slug, published);
  const mdxPath = join(postDir, "index.mdx");
  if (!existsSync(mdxPath)) {
    console.error(`[seo] no article at ${mdxPath}`);
    return null;
  }

  const keyword = primaryKeywordFor(slug, mdxPath);
  const calPathForEntry = join(root, cfg.paths.calendar);
  const calEntry = existsSync(calPathForEntry)
    ? loadJson(calPathForEntry).find((e) => e.slug === slug)
    : null;
  const firsthandNote = calEntry?.authorFirsthand
    ? `\n\nIMPORTANT — authorFirsthand piece: a real, named author (${cfg.author?.name || "the site author"}) wrote this personally from genuine first-hand experience. First-person voice and subjective opinions are LEGITIMATE Experience (positive E-E-A-T), NOT fabrication. Do not flag the personal voice as an EEAT/spam violation; only require citations for objective, verifiable facts (addresses, prices, dates, hours).`
    : "";
  const blogDir = join(root, cfg.paths.blogContentDir);
  const categories = [...readSlugs(join(root, cfg.paths.blogDataFile)).categories];
  const auditPath = join(postDir, "seo-review.json");
  const logFile = join(root, cfg.paths.logsDir, `seo-${slug}.log`);

  const prompt = `Audit this Comiida article.

- Article: ${mdxPath}
- Citations file (if present): ${join(postDir, "sources.json")}
- Primary keyword to target: ${keyword}
- Existing published posts (for internal-link validation): ${blogDir}
- Allowed category slugs: ${categories.join(", ")}
- Write your audit JSON to: ${auditPath}${firsthandNote}

Follow the rubric and output contract in your system prompt exactly.`;

  await runClaude({
    prompt,
    systemPromptFile: join(PIPELINE, "prompts/seo-review.system.md"),
    model: cfg.models.reviewer,
    allowedTools: "Read Glob Grep WebSearch Write",
    addDirs: [root],
    cwd: root,
    logFile,
  });

  if (!existsSync(auditPath)) {
    console.error(`[seo] audit not written: ${auditPath}`);
    return null;
  }
  try {
    return loadJson(auditPath);
  } catch (e) {
    console.error(`[seo] could not parse audit: ${e.message}`);
    return null;
  }
}

// CLI
if (import.meta.url === `file://${process.argv[1]}`) {
  const slug = process.argv[2];
  const published = process.argv.includes("--published");
  if (!slug) {
    console.error("Usage: seo-review.mjs <slug> [--published]");
    process.exit(1);
  }
  const audit = await runSeoReview(slug, { published });
  if (!audit) process.exit(1);
  const nb = (audit.blocking || []).length;
  const nf = (audit.topFixes || []).length;
  console.log(`[seo] ${slug}: ${audit.verdict} · ${audit.overall} · ${nb} blocking · ${nf} fixes`);
  if (audit.summary) console.log(`[seo] ${audit.summary}`);
  if (nb) console.log("[seo] blocking:\n  - " + audit.blocking.join("\n  - "));
  process.exit(audit.verdict === "pass" ? 0 : audit.verdict === "revise" ? 2 : 3);
}
