/**
 * Snippet Opportunity Ingestion
 * --------------------------------
 * Pulls non-captured snippet opportunities from existing audit data.
 * Makes NO new API calls.
 *
 * Internal module.
 */

import type { SnippetOpportunity, SnippetType } from "./types";
import type { AiSnippetEligibility } from "../audit/types";

const FEATURE_TO_SNIPPET_TYPE: Record<string, SnippetType> = {
  "Featured Snippet": "paragraph",
  "FAQ Rich Result": "faq",
  "People Also Ask": "faq",
  "Video Carousel": "video",
  "Product Carousel": "table",
  "Knowledge Panel": "definition",
  "AI Overview": "paragraph",
  "Local Pack": "paragraph",
  "Image Pack": "paragraph",
  "Reviews Carousel": "paragraph",
};

function featureToSnippetType(featureName: string): SnippetType {
  const direct = FEATURE_TO_SNIPPET_TYPE[featureName];
  if (direct) return direct;
  const lower = featureName.toLowerCase();
  if (/how.?to|step|procedure/.test(lower)) return "how_to";
  if (/list|top \d|best \d/.test(lower)) return "list_unordered";
  if (/faq|question/.test(lower)) return "faq";
  if (/table|compar|vs\./.test(lower)) return "table";
  if (/video/.test(lower)) return "video";
  if (/definition|what is|meaning/.test(lower)) return "definition";
  return "paragraph";
}

function querySnippetType(query: string, feature: string): SnippetType {
  const q = query.toLowerCase();
  if (/^how to\s/.test(q) || /steps to/.test(q)) return "how_to";
  if (/^what is\s|^define\s|meaning of/.test(q)) return "definition";
  if (/\bfaq\b|frequently asked/.test(q)) return "faq";
  if (/compare|vs\.?|difference between|which is better/.test(q)) return "table";
  if (/best \d|top \d|list of/.test(q)) return "list_unordered";
  return featureToSnippetType(feature);
}

function generateId(query: string, feature: string): string {
  return `${query.toLowerCase().replace(/\W+/g, "-").slice(0, 40)}-${feature.toLowerCase().replace(/\W+/g, "-").slice(0, 20)}`;
}

export function ingestSnippetOpportunities(existingAuditContext: {
  aiSnippetEligibility?: AiSnippetEligibility | null;
  serpAnalysis?: unknown;
}): SnippetOpportunity[] {
  const opportunities: SnippetOpportunity[] = [];
  const seenKeys = new Set<string>();

  const eligibility = existingAuditContext.aiSnippetEligibility;
  if (!eligibility) return [];

  for (const queryEntry of eligibility.perQuery) {
    const { query, perFeatureEligibility, brandRanking } = queryEntry;

    for (const feat of perFeatureEligibility) {
      if (feat.eligibilityStatus === "eligible") continue;

      const key = `${query.toLowerCase().trim()}::${feat.feature}`;
      if (seenKeys.has(key)) continue;
      seenKeys.add(key);

      const snippetType = querySnippetType(query, feat.feature);
      const status: SnippetOpportunity["status"] =
        feat.eligibilityStatus === "partial" ? "partially_captured" : "not_captured";

      // Rough search volume proxy from brand ranking
      let estimatedMonthlySearchVolume: number | null = null;
      if (brandRanking && brandRanking <= 5) estimatedMonthlySearchVolume = 800;
      else if (brandRanking && brandRanking <= 10) estimatedMonthlySearchVolume = 400;
      else if (brandRanking) estimatedMonthlySearchVolume = 150;

      opportunities.push({
        id: generateId(query, feat.feature),
        query,
        snippetType,
        currentCapturingDomain: null,
        estimatedMonthlySearchVolume,
        serpFeatures: [feat.feature],
        brandHasMatchingPage: brandRanking !== null,
        brandMatchingPageUrl: null,
        status,
      });
    }
  }

  // Also pull from topOpportunities for any missed
  for (const top of eligibility.topOpportunities) {
    const key = `${top.query.toLowerCase().trim()}::${top.feature}`;
    if (seenKeys.has(key)) continue;
    seenKeys.add(key);

    const snippetType = querySnippetType(top.query, top.feature);
    opportunities.push({
      id: generateId(top.query, top.feature),
      query: top.query,
      snippetType,
      currentCapturingDomain: null,
      estimatedMonthlySearchVolume: top.estimatedImpact?.toLowerCase().includes("high") ? 600 : 250,
      serpFeatures: [top.feature],
      brandHasMatchingPage: false,
      brandMatchingPageUrl: null,
      status: "not_captured",
    });
  }

  // Sort by estimated volume desc (nulls last), cap at 15
  return opportunities
    .sort((a, b) => {
      const av = a.estimatedMonthlySearchVolume ?? -1;
      const bv = b.estimatedMonthlySearchVolume ?? -1;
      return bv - av;
    })
    .slice(0, 15);
}
