/**
 * Extract Facebook Ad Library data from GraphQL/API JSON responses.
 */

import type { FbAdData } from "../../../types.js";
import { dig, asStr, asNum, asBool, asObj, edgeNodes } from "./helpers.js";

export function extractAds(
  body: Record<string, unknown>,
): FbAdData[] {
  try {
    // Multiple known paths for ad library results
    const adNodes =
      findAdNodes(body, "data", "ad_library_main", "search_results") ??
      findAdNodes(body, "data", "ad_library_page_info", "ads") ??
      findAdNodes(body, "data", "page", "ads") ??
      findAdNodes(body, "ads") ??
      findAdNodes(body, "data");

    if (!adNodes || adNodes.length === 0) {
      // Try single ad response
      const single = extractSingleAd(body);
      return single ? [single] : [];
    }

    const ads: FbAdData[] = [];
    for (const node of adNodes) {
      const ad = nodeToAd(node);
      if (ad) ads.push(ad);
    }
    return ads;
  } catch (err) {
    console.warn("[extractAds] Unexpected error:", (err as Error).message);
    return [];
  }
}

function findAdNodes(
  body: Record<string, unknown>,
  ...path: string[]
): Array<Record<string, unknown>> | null {
  const connection = dig(body, ...path);
  if (!connection) return null;

  // Could be an array directly or a connection with edges
  if (Array.isArray(connection)) {
    return connection.filter(
      (n): n is Record<string, unknown> => typeof n === "object" && n !== null,
    );
  }

  const nodes = edgeNodes<Record<string, unknown>>(connection);
  return nodes.length > 0 ? nodes : null;
}

function extractSingleAd(body: Record<string, unknown>): FbAdData | null {
  const node =
    (dig(body, "data", "node") as Record<string, unknown> | undefined) ??
    (dig(body, "data", "ad") as Record<string, unknown> | undefined);
  if (!node) return null;
  const typename = asStr(node, "__typename");
  if (typename && !["Ad", "AdLibraryAd"].includes(typename)) return null;
  return nodeToAd(node);
}

function nodeToAd(node: Record<string, unknown>): FbAdData | null {
  const adId =
    asStr(node, "ad_id") ??
    asStr(node, "id") ??
    asStr(node, "ad_archive_id");
  if (!adId) {
    console.warn("[extractAds] Skipping ad without id");
    return null;
  }

  const pageId =
    asStr(node, "page_id") ??
    asStr(dig(node, "page", "id") as Record<string, unknown> | undefined) ??
    asStr(dig(node, "page_info", "id") as Record<string, unknown> | undefined) ??
    "";

  const isActive =
    asBool(node, "is_active") ??
    (asStr(node, "ad_delivery_status") === "Active" ||
     asStr(node, "status") === "active");

  // Creative
  const snapshot =
    (dig(node, "snapshot") as Record<string, unknown> | undefined) ??
    (dig(node, "ad_creative") as Record<string, unknown> | undefined) ??
    node;

  const creativeText =
    asStr(snapshot, "body") ??
    asStr(dig(snapshot, "body", "text") as Record<string, unknown> | undefined) ??
    asStr(snapshot, "title") ??
    asStr(node, "ad_creative_body");

  const creativeImageUrl =
    asStr(dig(snapshot, "images", "0", "original_image_url") as Record<string, unknown> | undefined) ??
    asStr(dig(snapshot, "images", "0", "uri") as Record<string, unknown> | undefined) ??
    asStr(dig(snapshot, "image", "uri") as Record<string, unknown> | undefined) ??
    asStr(snapshot, "image_url");

  const creativeVideoUrl =
    asStr(dig(snapshot, "videos", "0", "video_hd_url") as Record<string, unknown> | undefined) ??
    asStr(dig(snapshot, "videos", "0", "video_sd_url") as Record<string, unknown> | undefined) ??
    asStr(snapshot, "video_url");

  // Spend
  const spendObj = (dig(node, "spend") as Record<string, unknown> | undefined) ?? node;
  const spendMin =
    asNum(spendObj, "lower_bound") ??
    asNum(spendObj, "spend_lower") ??
    asNum(node, "spend_min");
  const spendMax =
    asNum(spendObj, "upper_bound") ??
    asNum(spendObj, "spend_upper") ??
    asNum(node, "spend_max");

  // Impressions
  const impObj = (dig(node, "impressions") as Record<string, unknown> | undefined) ?? node;
  const impressionsMin =
    asNum(impObj, "lower_bound") ??
    asNum(impObj, "impressions_lower") ??
    asNum(node, "impressions_min");
  const impressionsMax =
    asNum(impObj, "upper_bound") ??
    asNum(impObj, "impressions_upper") ??
    asNum(node, "impressions_max");

  // Dates
  const startDate =
    asStr(node, "ad_delivery_start_time") ??
    asStr(node, "start_date") ??
    asStr(node, "ad_creation_time");
  const endDate =
    asStr(node, "ad_delivery_stop_time") ??
    asStr(node, "end_date");

  // Region distribution
  const regionDist = extractRegionDistribution(node);

  return {
    ad_id: adId,
    page_id: pageId,
    is_active: isActive,
    creative_text: creativeText,
    creative_image_url: creativeImageUrl,
    creative_video_url: creativeVideoUrl,
    start_date: startDate,
    end_date: endDate,
    spend_min: spendMin,
    spend_max: spendMax,
    impressions_min: impressionsMin,
    impressions_max: impressionsMax,
    region_distribution: regionDist,
    raw: asObj(node),
  };
}

function extractRegionDistribution(
  node: Record<string, unknown>,
): Record<string, number> | undefined {
  const dist =
    dig(node, "region_distribution") ??
    dig(node, "demographic_distribution") ??
    dig(node, "delivery_by_region");

  if (!Array.isArray(dist)) return undefined;

  const result: Record<string, number> = {};
  for (const entry of dist) {
    if (typeof entry !== "object" || !entry) continue;
    const e = entry as Record<string, unknown>;
    const region = asStr(e, "region") ?? asStr(e, "name") ?? asStr(e, "country");
    const pct = asNum(e, "percentage") ?? asNum(e, "pct");
    if (region && pct !== undefined) {
      result[region] = pct;
    }
  }

  return Object.keys(result).length > 0 ? result : undefined;
}
