"use server";

import { relay, type ActionFailure } from "@/lib/actions";
import { apiFetch } from "@/lib/api";
import { importListSchema, type ImportEntry } from "@/lib/imports";
import { importToTrackedJob, type TrackedJob } from "@/lib/job-toasts";
import { isJobFinished, jobSchema } from "@/lib/jobs";
import { requireSession } from "@/lib/session";
import { siteListSchema } from "@/lib/sites";

// Source « imports » du toaster global (règle du 2026-09-01) : la seule à ce jour. Crawl et
// Search Console fourniront la leur, de la même forme, et `JobsToaster` les additionnera
// sans changer d'une ligne.
//
// Volontairement bornée à la **première page** du listing : ce qui tourne est forcément
// récent. Un travail plus vieux que 25 imports n'a plus rien à annoncer.

const PAGE_SIZE = 25;
/** Plafond de l'api pour `/sites` : une organisation plus large perdra le lien, pas le toast. */
const SITES_LIMIT = 100;

/**
 * Travaux d'import de l'organisation active, vus par la couche de notification.
 *
 * Rend **toute** la première page, pas seulement les travaux actifs : c'est
 * `reconcileToasts` qui sait lesquels ont un toast ouvert et doivent donc être conclus. Un
 * filtrage côté serveur ferait disparaître un import au moment précis où il se termine, et
 * son toast resterait éternellement en « en cours ».
 */
export async function importJobsAction(): Promise<{ jobs: TrackedJob[] } | ActionFailure> {
  const { session } = await requireSession();
  // Parcours d'onboarding : l'api refuserait de toute façon un jeton sans `org_id`.
  if (!session.activeOrganizationId) return { jobs: [] };

  try {
    const [listing, sites] = await Promise.all([
      apiFetch<unknown>(`/imports?limit=${PAGE_SIZE}`).then((raw) => importListSchema.parse(raw)),
      // `site_target_id` est tout ce que rend le listing, or la page de rapport vit sous
      // `/sites/<id>/…` : la correspondance cible → site se reconstruit ici.
      apiFetch<unknown>(`/sites?limit=${SITES_LIMIT}`).then((raw) => siteListSchema.parse(raw)),
    ]);
    const siteByTarget = new Map<string, string>();
    for (const site of sites.items) {
      for (const target of site.targets) siteByTarget.set(target.id, site.id);
    }
    const progress = await loadProgress(listing.items);
    return {
      jobs: listing.items.map((entry) =>
        importToTrackedJob(
          entry,
          (entry.site_target_id && siteByTarget.get(entry.site_target_id)) ?? null,
          progress.get(entry.id) ?? null,
        ),
      ),
    };
  } catch (error) {
    return relay(error);
  }
}

/**
 * Progression des seuls imports encore en cours, lue sur le socle jobs.
 *
 * Le listing des imports ne porte que le rapport **final** : sans cet aller-retour, le toast
 * n'aurait rien à dire pendant les deux minutes où le fichier se traite — exactement le
 * silence que la règle interdit. Un job illisible (disparu, api qui bronche) ne fait pas
 * échouer le tour : le toast retombe sur « Préparation du fichier… ».
 */
async function loadProgress(
  entries: readonly ImportEntry[],
): Promise<Map<string, Record<string, unknown>>> {
  const running = entries.filter(
    (entry) => entry.entry === "import" && !isJobFinished(entry.status),
  );
  const progress = new Map<string, Record<string, unknown>>();
  await Promise.all(
    running.map(async (entry) => {
      if (entry.entry !== "import") return;
      try {
        const job = jobSchema.parse(await apiFetch<unknown>(`/jobs/${entry.job_id}`));
        if (job.progress) progress.set(entry.id, job.progress);
      } catch {
        // Sans progression, le toast reste ouvert et le dit : il ne doit surtout pas
        // disparaître parce qu'un compteur n'a pas pu être lu.
      }
    }),
  );
  return progress;
}
