/**
 * Action Queue shared types.
 *
 * The Action Queue (a.k.a. Opportunity Map) is the operator's single
 * "what do I fix next" surface. Every row is normalized from one of five
 * source types into a canonical contract by the backend
 * (OpportunityAggregatorService + ActionQueueContractEnricher).
 *
 * Canonical fields (`source_type`, `source_id`, `primary_issue`,
 * `recommended_action`, `confidence_score`, `data_window_days`,
 * `last_sync_at`, `draftable`, `primary_cta`) are preferred by the UI.
 * Legacy fallback fields (`opportunity_type`, `opportunity_id`, `issue`,
 * `action`, `confidence`, `evidence`, `demand`, `reasoning`,
 * `lifecycle_status`, `keyword_opportunity_type`) keep older payloads — and
 * a backend that has not finished migrating — rendering safely.
 *
 * This file is the single source of truth: OpportunityMap/Index.tsx and
 * UnifiedCard.tsx import from here instead of redeclaring interfaces.
 */

/** The five sources that feed the Action Queue. */
export type ActionQueueSourceType =
  | 'recommendation'
  | 'freshness'
  | 'cannibalization'
  | 'topic_gap'
  | 'keyword_opportunity';

/**
 * Primary call-to-action surfaced on a row. Mirrors App\Enums\PrimaryCta
 * (string values are the contract — keep these in sync with the backend enum).
 */
export type PrimaryCta =
  | 'generate_draft'
  | 'view_draft'
  | 'review_evidence'
  | 'view_detail'
  | 'connect_wp'
  | 'connect_gsc'
  | 'run_analysis'
  | 'create_brief';

/**
 * Per-row data-health signal computed at enrich time (never persisted).
 * Severity order: `missing > stale > partial > fresh`. `fresh` rows render no
 * badge; everything else surfaces a `DataHealthBadge` with the `reasons` copy.
 */
export type ActionQueueDataHealth = 'fresh' | 'stale' | 'partial' | 'missing';

/** Sub-classification for keyword opportunities. */
export type KeywordOpportunityType =
  | 'striking_distance'
  | 'ctr_gap'
  | 'rising_query'
  | 'content_gap';

/**
 * Demand summary. Fields are optional and source-specific; only the keys
 * relevant to a given `source_type` are populated.
 */
export interface ActionQueueDemand {
  // Recommendation / Freshness — click trend
  clicks_before?: number;
  clicks_after?: number;
  delta_percent?: number;
  days_declining?: number;
  // Freshness — real PageDecaySignal columns
  baseline_7d_clicks?: number;
  baseline_28d_clicks?: number;
  baseline_90d_clicks?: number;
  current_clicks?: number;
  decay_magnitude?: number;
  freshness_score?: number;
  content_age_days?: number;
  // Cannibalization
  query_cluster?: string;
  competing_pages?: number;
  total_clicks?: number;
  volatility?: number;
  // Topic gap
  cluster_name?: string;
  cluster_demand?: number;
  coverage_percentage?: number;
  missing_entity?: string;
  effort?: number | string;
  risk?: number | string;
  // Keyword opportunity
  query?: string;
  position?: number;
  impressions?: number;
  clicks?: number;
  ctr?: number;
  expected_ctr?: number;
  ctr_gap_percent?: number;
  growth_percent?: number;
  baseline_impressions?: number;
  latest_impressions?: number;
  weeks_tracked?: number;
  metadata?: Record<string, unknown>;
}

/**
 * Free-form evidence bag. Source-specific keys are read by EvidencePanel;
 * NEVER rendered as raw JSON.
 */
export type ActionQueueEvidence = Record<string, unknown>;

/** A related-opportunity link rendered in the card's detail drawer. */
export interface RelatedOpportunity {
  type: ActionQueueSourceType;
  id: number;
  relationship: string;
}

/**
 * A single normalized Action Queue row.
 *
 * `source_*` / canonical fields are preferred; the legacy `opportunity_*`
 * fields are required because some payloads (and the keying in Index.tsx)
 * still depend on them. The UI reads `canonical ?? legacy` everywhere.
 */
export interface ActionQueueOpportunity {
  // --- Canonical contract (preferred) ---
  source_type?: ActionQueueSourceType | string;
  source_id?: number;
  /**
   * AMEND ENGR-012 / pack-13: recommendation public_id (26-char ULID).
   * Non-null only for source_type='recommendation' rows.
   * OpportunityMap uses this to dispatch ScopeCostModal with ULIDs instead of
   * integer PKs (eliminating the QAR-003 integer compat path in BatchCohortResolver).
   */
  source_public_id?: string | null;
  primary_issue?: string;
  recommended_action?: string;
  confidence_score?: number;
  data_window_days?: number | null;
  last_sync_at?: string | null;
  draftable?: boolean;
  primary_cta?: PrimaryCta;
  // Per-row data-health signal (computed at enrich time by the backend
  // ActionQueueContractEnricher::resolveDataHealth — see app/Services). `fresh`
  // rows omit the badge; `stale`/`partial`/`missing` surface it with
  // `data_health_reasons`.
  // `data_sources` labels which integrations fed the row (e.g. 'gsc','wordpress').
  data_health?: ActionQueueDataHealth;
  data_health_reasons?: string[];
  data_sources?: string[];

  // --- Legacy fallback (always present today) ---
  opportunity_type: ActionQueueSourceType;
  opportunity_id: number;
  page_url: string;
  demand: ActionQueueDemand | null;
  issue: string;
  action: string;
  confidence: number;
  impact_score: number;
  reasoning: string;
  evidence?: ActionQueueEvidence;
  status?: string;
  lifecycle_status?: string;
  // Draft-eligibility contract: surfaced by ActionQueueContractEnricher so the
  // UI can route to an existing draft instead of offering a duplicate. Only
  // recommendation rows carry a meaningful draft state; other sources report
  // has_draft=false / latest_draft_id=null.
  has_draft?: boolean;
  latest_draft_id?: number | null;
  // Topic-gap rows carry the target cluster id separately from `opportunity_id`
  // (which is the gap-suggestion id). The card routes topic_gap detail links via
  // this field — see OpportunityFetcher.php emitting `topic_cluster_id`.
  topic_cluster_id?: number;
  related_opportunities?: RelatedOpportunity[];
  keyword_opportunity_type?: KeywordOpportunityType;

  // --- Record-level signal fields (freshness): these live on the source
  // record, NOT inside `demand`. EvidencePanel reads them via its `record` prop.
  urgency_score?: number;
  freshness_score?: number;
}
