// src/lib/keywords/prompt/actions.ts — server action: assemble the prompt bundle for one keyword.
"use server";
import { eq } from "drizzle-orm";
import { buildContentAnalysis } from "@/lib/content/analysis";
import { db } from "@/lib/db/client";
import { keywords } from "@/lib/db/domain-schema";
import { deriveSiteGscState } from "@/lib/gsc/site-state";
import { getSitesForActiveOrg } from "@/lib/sites/access";
import type { KeywordPromptBundle, ReadyState } from "./types";

/** Map `ContentAnalysis.serp.state` (ok|none) → ReadyState. */
function mapSerp(state: "ok" | "none"): ReadyState {
  return state === "ok" ? "ready" : "missing";
}

/** Map `ContentAnalysis.onpage.state` (ok|partial|none) → ReadyState. */
function mapOnpage(state: "ok" | "partial" | "none"): ReadyState {
  if (state === "ok") return "ready";
  if (state === "partial") return "progress";
  return "missing";
}

/** Map `ContentAnalysis.brief.state` (ok|other-locale|none) → ReadyState. */
function mapBrief(state: "ok" | "other-locale" | "none"): ReadyState {
  if (state === "ok") return "ready";
  if (state === "other-locale") return "progress";
  return "missing";
}

/**
 * Everything the prompt builder + completeness card need for one keyword.
 *
 * Org guard: resolve the active org's visible sites via `getSitesForActiveOrg()`
 * and assert the keyword's `siteId` is among them — a keyword in another org's
 * site (or a missing row) throws. This reuses the member-scoped access list, so a
 * member cannot pull a bundle for a site that wasn't shared with them.
 */
export async function getKeywordPromptBundle(
  keywordId: string,
): Promise<KeywordPromptBundle> {
  const [row] = await db
    .select()
    .from(keywords)
    .where(eq(keywords.id, keywordId))
    .limit(1);
  if (!row) {
    throw new Error("Keyword does not belong to the active organization");
  }

  const visibleSiteIds = new Set(
    (await getSitesForActiveOrg()).map((s) => s.id),
  );
  if (!visibleSiteIds.has(row.siteId)) {
    throw new Error("Keyword does not belong to the active organization");
  }

  // Connexion GSC = état de CE site. La carte de complétude distingue
  // « connecté, positions pas encore remontées » de « non connecté ».
  const gscConnected = (await deriveSiteGscState(row.siteId)).state === "ok";

  const country = row.country;
  const lang = row.lang;
  const snapshot = row.serp?.[country] ?? null;
  const metrics = row.metrics?.[row.primarySource] ?? null;
  const semanticProfile = row.semantic?.[country] ?? null;

  const analysis = buildContentAnalysis(row.serp, row.briefs, country);

  return {
    id: row.id,
    term: row.term,
    country,
    lang,
    intent: row.intent,
    primarySource: row.primarySource,
    sources: row.sources,
    vol: metrics?.volume ?? null,
    kd: metrics?.kd ?? null,
    cpc: metrics?.cpc ?? null,
    competitors: snapshot?.competitors ?? [],
    serpFeatures: snapshot?.features ?? [],
    brief: row.briefs?.[country] ?? null,
    hasGsc: !!row.gsc || Object.keys(row.gscByCountry ?? {}).length > 0,
    gscConnected,
    semanticProfile,
    readiness: {
      serp: mapSerp(analysis.serp.state),
      onpage: mapOnpage(analysis.onpage.state),
      brief: mapBrief(analysis.brief.state),
    },
  };
}
