// src/lib/onboarding/activation/derive.ts — complétion dérivée de l'état produit.
// La vérité vit dans la DB ; rien n'est stocké, donc rien ne se désynchronise.
//
// La checklist est dérivée PAR SITE : dérivée par organisation, elle arrivait
// déjà cochée sur le deuxième site et le coéquipier à qui on confie un nouveau
// site ne voyait jamais le guide. Deux drapeaux restent par organisation :
// `create-site` (« l'org a-t-elle un site ? » — la question précède le site
// actif) et `invite-teammate` (inviter quelqu'un n'est pas une action de site).
import "server-only";

import { and, eq, inArray, isNotNull, ne, sql } from "drizzle-orm";
import { invitation, member } from "@/lib/db/auth-schema";
import { db } from "@/lib/db/client";
import { keywords, keywordTargets, sites } from "@/lib/db/domain-schema";
import { EXCLUDED_TARGET_STATUSES } from "@/lib/keywords/targets";
import type { ActivationFlags } from "./types";

async function exists(query: PromiseLike<unknown[]>): Promise<boolean> {
  return (await query).length > 0;
}

/** Drapeaux d'activation de l'org `orgId` vus depuis le site actif `siteId`.
 *  `siteId` à `null` (aucun site accessible) → les quatre drapeaux de site sont
 *  faux sans la moindre requête ; les deux drapeaux d'organisation, eux, sont
 *  toujours calculés.
 *
 *  ⚠️ Contrat d'appel : `siteId` DOIT appartenir à `orgId` et être accessible à
 *  l'appelant — `getActivationSnapshot` le résout depuis `getSitesForActiveOrg`,
 *  qui filtre déjà par org et par accès. Les quatre drapeaux de site
 *  (`connect-gsc`, `add-keywords`, `enrich-keywords`, `target-country`)
 *  recroisent malgré tout `sites.org_id` : un couple mal apparié les laisse
 *  uniformément faux au lieu d'en cocher certains. */
export async function deriveActivationFlags(
  orgId: string,
  siteId: string | null,
): Promise<ActivationFlags> {
  const one = { one: sql<number>`1` };

  // Les deux drapeaux par organisation : on les calcule toujours, même sans
  // site actif (un member sans accès à aucun site voit quand même que l'org a
  // franchi la création de site).
  const orgFlagsPromise = Promise.all([
    exists(db.select(one).from(sites).where(eq(sites.orgId, orgId)).limit(1)),
    (async () =>
      (await exists(
        db
          .select(one)
          .from(member)
          .where(
            and(eq(member.organizationId, orgId), ne(member.role, "owner")),
          )
          .limit(1),
      )) ||
      (await exists(
        db
          .select(one)
          .from(invitation)
          .where(
            and(
              eq(invitation.organizationId, orgId),
              eq(invitation.status, "pending"),
            ),
          )
          .limit(1),
      )))(),
  ]);

  if (siteId === null) {
    const [hasSite, hasTeammate] = await orgFlagsPromise;
    return {
      "create-site": hasSite,
      "connect-gsc": false,
      "add-keywords": false,
      "enrich-keywords": false,
      "target-country": false,
      "invite-teammate": hasTeammate,
    };
  }

  const [hasGsc, hasKeyword, hasEnrichment, hasTarget, orgFlags] =
    await Promise.all([
      // On ne teste pas `gscAuthorizedAccountId` : les mappings antérieurs à la
      // refonte l'ont légitimement à NULL et empruntent une branche de
      // compatibilité. On ne passe pas non plus par `deriveSiteGscState` : la
      // question posée est « as-tu fait cette étape ? », pas « l'intégration
      // est-elle en bonne santé maintenant ? ».
      exists(
        db
          .select(one)
          .from(sites)
          .where(
            and(
              eq(sites.id, siteId),
              eq(sites.orgId, orgId),
              isNotNull(sites.gscProperty),
              isNotNull(sites.gscAuthorizedUserId),
            ),
          )
          .limit(1),
      ),
      // La jointure sur `sites` ne sert pas à retrouver le site (on a son id) :
      // elle porte le croisement avec l'organisation, au même titre que le
      // `eq(sites.orgId, orgId)` de `connect-gsc`. Sans elle, les drapeaux de
      // mots-clés seraient les seuls non étanches à l'org — une asymétrie
      // invisible qui cocherait des étapes jamais franchies pour un appelant
      // passant un couple (org, site) mal apparié. Coût : une jointure sur PK.
      exists(
        db
          .select(one)
          .from(keywords)
          .innerJoin(sites, eq(keywords.siteId, sites.id))
          .where(and(eq(keywords.siteId, siteId), eq(sites.orgId, orgId)))
          .limit(1),
      ),
      // `volume_primary` est GENERATED depuis `metrics -> primary_source` : il
      // mesure l'enrichissement réel, contrairement à `enrichment_exports` qui
      // ne traçait que les exports vers un outil tiers — un aller-retour de
      // fichier cochait l'étape sans qu'aucune métrique ne soit revenue.
      exists(
        db
          .select(one)
          .from(keywords)
          .innerJoin(sites, eq(keywords.siteId, sites.id))
          .where(
            and(
              eq(keywords.siteId, siteId),
              eq(sites.orgId, orgId),
              isNotNull(keywords.volumePrimary),
            ),
          )
          .limit(1),
      ),
      // `planned` OU `live` — la MÊME frontière que celle qui retire un mot-clé
      // des candidats du premier ciblage, d'où la constante partagée plutôt
      // qu'un littéral (voir `EXCLUDED_TARGET_STATUSES`). `candidate` ne suffit
      // pas : c'est encore une hypothèse, et c'est le statut par défaut du
      // ciblage en masse.
      //
      // ⚠️ Ne JAMAIS restreindre à `planned` seul : le drapeau se dé-cocherait
      // au premier succès. Un plan publié passe `planned → live`
      // (`createContentItemAction`), et l'utilisateur qui sort du parcours avec
      // un unique ciblage peut le retirer d'un clic dans la matrice Recherche —
      // dans les deux cas `requiredComplete` redevenait faux et la navigation
      // suivante renvoyait en plein écran sur le parcours d'entrée.
      exists(
        db
          .select(one)
          .from(keywordTargets)
          .innerJoin(sites, eq(keywordTargets.siteId, sites.id))
          .where(
            and(
              eq(keywordTargets.siteId, siteId),
              eq(sites.orgId, orgId),
              inArray(keywordTargets.status, EXCLUDED_TARGET_STATUSES),
            ),
          )
          .limit(1),
      ),
      orgFlagsPromise,
    ]);

  const [hasSite, hasTeammate] = orgFlags;

  return {
    "create-site": hasSite,
    "connect-gsc": hasGsc,
    "add-keywords": hasKeyword,
    "enrich-keywords": hasEnrichment,
    "target-country": hasTarget,
    "invite-teammate": hasTeammate,
  };
}
