// src/lib/keywords/overview.ts — pure aggregation for the keyword overview.
import type { BacklinkStatus } from "@/lib/db/domain-schema";
import { opportunityScore, positionBucket } from "./gsc-positioning";
import type { Keyword, SearchIntent } from "./types";

export interface CountBy<K> {
  key: K;
  count: number;
}
export interface OpportunityRow {
  id: string;
  term: string;
  volume: number;
  position: number;
  kd: number;
  impressions: number;
  score: number;
}
export interface TopVolumeRow {
  id: string;
  term: string;
  volume: number;
  position: number | null;
}
export interface KeywordOverview {
  totalKeywords: number;
  positioned: number;
  positionedPct: number;
  avgPosition: number | null;
  totalClicks: number;
  totalImpressions: number;
  avgCtr: number | null;
  quickWins: number;
  totalVolume: number;
  avgKd: number | null;
  positionDistribution: CountBy<
    "top3" | "top10" | "quickwin" | "deep" | "far" | "none"
  >[];
  kdDistribution: CountBy<string>[];
  byIntent: CountBy<SearchIntent>[];
  byTheme: CountBy<string>[];
  topByVolume: TopVolumeRow[];
  opportunities: OpportunityRow[];
  stages: { reservoir: number; research: number; planning: number };
  backlinks: { total: number; byStatus: CountBy<BacklinkStatus>[] } | null;
}

const POSITION_BUCKETS = [
  "top3",
  "top10",
  "quickwin",
  "deep",
  "far",
  "none",
] as const;
const KD_BUCKETS = ["0-20", "21-40", "41-60", "61-80", "81-100"] as const;
const INTENTS: SearchIntent[] = [
  "informational",
  "commercial",
  "transactional",
  "navigational",
  "local",
];

function kdBucket(kd: number): string {
  if (kd <= 20) return "0-20";
  if (kd <= 40) return "21-40";
  if (kd <= 60) return "41-60";
  if (kd <= 80) return "61-80";
  return "81-100";
}

const round1 = (n: number) => Math.round(n * 10) / 10;

/** Build the overview aggregate for a site's keywords (+ optional backlink rows). */
export function buildKeywordOverview(
  keywords: Keyword[],
  backlinks: { status: BacklinkStatus }[] | null,
): KeywordOverview {
  const total = keywords.length;
  const positionedKw = keywords.filter((k) => k.gsc != null);
  const totalClicks = positionedKw.reduce(
    (s, k) => s + (k.gsc?.clicks ?? 0),
    0,
  );
  const totalImpressions = positionedKw.reduce(
    (s, k) => s + (k.gsc?.impressions ?? 0),
    0,
  );
  const totalVolume = keywords.reduce(
    (s, k) => s + (k.metrics[k.primarySource]?.volume ?? 0),
    0,
  );
  const kds = keywords
    .map((k) => k.metrics[k.primarySource]?.kd)
    .filter((v): v is number => typeof v === "number");

  const posCounts = new Map<string, number>(
    POSITION_BUCKETS.map((b) => [b, 0]),
  );
  for (const k of keywords) {
    const b = positionBucket(k.gsc);
    posCounts.set(b, (posCounts.get(b) ?? 0) + 1);
  }
  const kdCounts = new Map<string, number>(KD_BUCKETS.map((b) => [b, 0]));
  for (const kd of kds)
    kdCounts.set(kdBucket(kd), (kdCounts.get(kdBucket(kd)) ?? 0) + 1);

  const intentCounts = new Map<SearchIntent, number>(
    INTENTS.map((i) => [i, 0]),
  );
  for (const k of keywords)
    for (const i of k.intent)
      intentCounts.set(i, (intentCounts.get(i) ?? 0) + 1);

  const themeCounts = new Map<string, number>();
  for (const k of keywords) {
    const key = k.theme ?? "—";
    themeCounts.set(key, (themeCounts.get(key) ?? 0) + 1);
  }

  const opportunities: OpportunityRow[] = keywords
    .map((k) => ({ k, score: opportunityScore(k.gsc) }))
    .filter((x) => x.score > 0 && x.k.gsc != null)
    .sort((a, b) => b.score - a.score)
    .slice(0, 8)
    .map(({ k, score }) => ({
      id: k.id,
      term: k.term,
      volume: k.metrics[k.primarySource]?.volume ?? 0,
      position: k.gsc?.position ?? 0,
      kd: k.metrics[k.primarySource]?.kd ?? 0,
      impressions: k.gsc?.impressions ?? 0,
      score,
    }));

  const topByVolume: TopVolumeRow[] = [...keywords]
    .sort(
      (a, b) =>
        (b.metrics[b.primarySource]?.volume ?? 0) -
        (a.metrics[a.primarySource]?.volume ?? 0),
    )
    .slice(0, 10)
    .map((k) => ({
      id: k.id,
      term: k.term,
      volume: k.metrics[k.primarySource]?.volume ?? 0,
      position: k.gsc?.position ?? null,
    }));

  const blCounts = new Map<BacklinkStatus, number>();
  if (backlinks)
    for (const b of backlinks)
      blCounts.set(b.status, (blCounts.get(b.status) ?? 0) + 1);

  return {
    totalKeywords: total,
    positioned: positionedKw.length,
    positionedPct: total ? Math.round((positionedKw.length / total) * 100) : 0,
    avgPosition: positionedKw.length
      ? round1(
          positionedKw.reduce((s, k) => s + (k.gsc?.position ?? 0), 0) /
            positionedKw.length,
        )
      : null,
    totalClicks,
    totalImpressions,
    avgCtr: totalImpressions ? totalClicks / totalImpressions : null,
    quickWins: posCounts.get("quickwin") ?? 0,
    totalVolume,
    avgKd: kds.length
      ? Math.round(kds.reduce((s, v) => s + v, 0) / kds.length)
      : null,
    positionDistribution: POSITION_BUCKETS.map((key) => ({
      key,
      count: posCounts.get(key) ?? 0,
    })),
    kdDistribution: KD_BUCKETS.map((key) => ({
      key,
      count: kdCounts.get(key) ?? 0,
    })),
    byIntent: INTENTS.map((key) => ({ key, count: intentCounts.get(key) ?? 0 }))
      .filter((x) => x.count > 0)
      .sort((a, b) => b.count - a.count),
    byTheme: [...themeCounts.entries()]
      .map(([key, count]) => ({ key, count }))
      .sort((a, b) => b.count - a.count)
      .slice(0, 6),
    topByVolume,
    opportunities,
    stages: {
      reservoir: total,
      research: keywords.filter((k) => k.stages.includes("research")).length,
      planning: keywords.filter((k) => k.stages.includes("planning")).length,
    },
    backlinks: backlinks
      ? {
          total: backlinks.length,
          byStatus: [...blCounts.entries()]
            .map(([key, count]) => ({ key, count }))
            .sort((a, b) => b.count - a.count),
        }
      : null,
  };
}
