#!/usr/bin/env node
/**
 * @agent-manifest
 * name:        publish-tick
 * title:       Auto-Publisher Tick
 * class:       plumbing
 * trigger:     cron:run.sh publish-tick.mjs(15m)
 * description: Once per day after the morning floor, publish ONE eligible draft (SEO-gated) with a randomized earlier-today timestamp. No LLM.
 * model:       -
 * prompt:      -
 * skills:      -
 * tools:       lib/publish.mjs
 * reads:       -
 * writes:      -
 * created:     2026-07-04
 * @end
 */
// Auto-publisher. Run frequently by cron (e.g. every 15 min). Once per day, after a morning
// floor time, it publishes ONE eligible draft (passing the SEO gate) so posts go live in the
// morning — but stamps each with a RANDOM earlier-today timestamp so published times aren't a
// fixed-minute metronome. News drafts are fast-tracked ahead of evergreen.
// Usage: node agents/scripts/publish-tick.mjs [--now]   (--now ignores the floor)
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync } from "node:fs";
import { dirname, resolve, join } from "node:path";
import { fileURLToPath } from "node:url";
import { loadJson, todayInTz, randomTimestampTodaySoFar } from "./lib/util.mjs";
import { promoteDraft } from "./lib/publish.mjs";

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

if (!cfg.publish?.auto) {
  console.log("[publish-tick] publish.auto is false — nothing to do.");
  process.exit(0);
}

const today = todayInTz(cfg.editorial.timezone);
const stateDir = join(root, cfg.paths.stateDir);
mkdirSync(stateDir, { recursive: true });
const statePath = join(stateDir, `publish-${today}.json`);

let state = existsSync(statePath) ? loadJson(statePath) : { published: false, slug: null };

if (state.published) {
  console.log(`[publish-tick] already published today (${state.slug}).`);
  process.exit(0);
}

// Morning floor: don't publish before this Bogotá time (gives the 06:00 writer time to draft +
// audit + auto-revise so the day's freshest article is the one that goes live).
const notBefore = cfg.publish.notBefore || "07:00";
const floorMs = Date.parse(`${today}T${notBefore}:00-05:00`);
if (!FORCE_NOW && Date.now() < floorMs) {
  console.log(`[publish-tick] waiting — floor ${notBefore} Bogotá (${new Date(floorMs).toISOString()}).`);
  process.exit(0);
}

// Find an eligible draft: on disk, has cover, and passes the SEO gate.
const minScore = cfg.seo?.minScore ?? 85;
const draftsRoot = join(root, cfg.paths.draftsDir);
const calPath = join(root, cfg.paths.calendar);
const calendar = existsSync(calPath) ? loadJson(calPath) : [];
const entryOf = (slug) => calendar.find((e) => e.slug === slug) || {};
const dateOf = (slug) => entryOf(slug).date || "9999-12-31";

function eligible(slug) {
  const d = join(draftsRoot, slug);
  if (!existsSync(join(d, "index.mdx")) || !existsSync(join(d, "cover.jpg"))) return false;
  const auditPath = join(d, "seo-review.json");
  if (!existsSync(auditPath)) return false;
  try {
    const a = loadJson(auditPath);
    return a.verdict !== "fail" && (a.overall ?? 0) >= minScore;
  } catch {
    return false;
  }
}

const candidates = existsSync(draftsRoot)
  ? readdirSync(draftsRoot, { withFileTypes: true })
      .filter((x) => x.isDirectory())
      .map((x) => x.name)
      .filter(eligible)
      // News drafts first (hottest, then by date); evergreen after, oldest first.
      .sort((a, b) => {
        const ea = entryOf(a);
        const eb = entryOf(b);
        const na = ea.news ? 1 : 0;
        const nb = eb.news ? 1 : 0;
        if (na !== nb) return nb - na;
        if (na) return (eb.freshnessScore ?? 0) - (ea.freshnessScore ?? 0) || dateOf(a).localeCompare(dateOf(b));
        return dateOf(a).localeCompare(dateOf(b));
      })
  : [];

if (!candidates.length) {
  console.log("[publish-tick] no SEO-passing draft ready to publish (leaving queue as-is).");
  process.exit(0);
}

const slug = candidates[0];
const stamp = randomTimestampTodaySoFar(cfg.editorial.timezone); // morning go-live, random timestamp
console.log(`[publish-tick] auto-publishing "${slug}" with timestamp ${stamp}…`);

try {
  const { url, taxonomyAdded } = await promoteDraft(cfg, slug, { dateOverride: stamp });
  if (taxonomyAdded.length) console.log(`[publish-tick] registered: ${taxonomyAdded.join(", ")}`);
  state = { published: true, slug, publishedAt: stamp };
  writeFileSync(statePath, JSON.stringify(state, null, 2));
  console.log(`[publish-tick] LIVE → ${url}`);
} catch (e) {
  console.error(`[publish-tick] publish failed: ${e.message}`);
  process.exit(1);
}
