import type { KeywordSource, MonthlyVolume, SourceMetrics } from "./types";

// The subset of the keywords row the merge cares about (Drizzle select shape).
export type MergeableKeyword = {
  id: string;
  siteId: string;
  term: string;
  lang: string;
  country: string;
  theme: string | null;
  status: string;
  intent: string[];
  stages: string[];
  primarySource: string;
  sources: string[];
  metrics: Partial<Record<KeywordSource, SourceMetrics>>;
  trend: MonthlyVolume[];
  serpFeatures: string[] | null;
  briefs: Record<string, unknown>;
  gsc: { position?: number } | null;
  serp: Record<string, unknown>;
};

const STATUS_RANK: Record<string, number> = {
  active: 3,
  watch: 2,
  idea: 1,
  archived: 0,
};

const uniq = <T>(xs: T[]): T[] => [...new Set(xs)];

/** True if the string contains any non-ASCII (e.g. accented) character. */
const hasNonAscii = (s: string): boolean => {
  for (const ch of s) {
    if ((ch.codePointAt(0) ?? 0) > 127) return true;
  }
  return false;
};

/** Merge keyword rows that collapse to `canonicalId` into one survivor row. */
export function mergeKeywords(
  canonicalId: string,
  rows: MergeableKeyword[],
): MergeableKeyword {
  // Survivor: the row already at canonicalId, else the first.
  const survivor = rows.find((r) => r.id === canonicalId) ?? rows[0];
  // Prefer an accented term (richer) for display: any term with a non-ASCII char
  // (e.g. "électricité") is the richer variant of a collapsed group, regardless
  // of row order. Falls back to the survivor's term if none are accented.
  const term = rows.find((r) => hasNonAscii(r.term))?.term ?? survivor.term;

  let status = survivor.status;
  let gsc = survivor.gsc;
  let trend = survivor.trend;
  const metrics: Partial<Record<KeywordSource, SourceMetrics>> = {};
  const briefs: Record<string, unknown> = {};
  let intent: string[] = [];
  let stages: string[] = [];
  let sources: string[] = [];

  for (const r of rows) {
    if ((STATUS_RANK[r.status] ?? 0) > (STATUS_RANK[status] ?? 0))
      status = r.status;
    if (
      r.gsc &&
      (!gsc || (r.gsc.position ?? Infinity) < (gsc.position ?? Infinity))
    )
      gsc = r.gsc;
    if (r.trend.length > trend.length) trend = r.trend;
    Object.assign(metrics, r.metrics);
    Object.assign(briefs, r.briefs);
    intent = intent.concat(r.intent);
    stages = stages.concat(r.stages);
    sources = sources.concat(r.sources);
  }

  return {
    ...survivor,
    id: canonicalId,
    term,
    status,
    gsc,
    trend,
    metrics,
    briefs,
    intent: uniq(intent),
    stages: uniq(stages),
    sources: uniq(sources),
  };
}
