/**
 * Alerting option lists and enum types used by the rule editor.
 *
 * These are **string-literal unions mirrored by hand from the backend Java
 * enums**. Why not derived from `schema.d.ts`? Springdoc emits polymorphic
 * `@JsonSubTypes` conditions with two conflicting `kind` discriminators (the
 * class-name literal + the Jackson enum), whose intersection is `never` —
 * indexed access like `RouteMetricCondition['comparator']` resolves to
 * `never` and silently breaks `Record<T, string>` exhaustiveness. Until the
 * OpenAPI shape is fixed upstream, we declare the unions here.
 *
 * How to keep this file honest:
 *   1. Regenerate the schema (`npm run generate-api:live` or `generate-api`).
 *   2. Search `src/api/schema.d.ts` for the enum unions on the relevant
 *      condition type (e.g. `comparator?: "GT" | "GTE" | …`).
 *   3. Update the matching union below.
 *   4. `enums.test.ts` dumps every exported `*_OPTIONS` array as an inline
 *      snapshot — the diff in a PR shows exactly which values / labels moved.
 *
 * Fields whose backend type is `String` (agent state, log level, deployment
 * states, exchange filter status, JVM metric names) aren't in here at all —
 * springdoc emits them as open-ended strings. Follow-up: add
 * `@Schema(allowableValues = …)` on the Java record components, then migrate
 * those string literals into this file too.
 *
 * Condition-kind-wide enums (ConditionKind, Severity, TargetKind) are
 * derived from `schema.d.ts`, because they appear on flat fields that
 * springdoc types correctly.
 */
import type { components } from '../../api/schema';

type AlertRuleRequest = components['schemas']['AlertRuleRequest'];
type AlertRuleTarget  = components['schemas']['AlertRuleTarget'];

// Derived — these schema fields are correctly typed (flat, no polymorphism).
export type ConditionKind = NonNullable<AlertRuleRequest['conditionKind']>;
export type Severity      = NonNullable<AlertRuleRequest['severity']>;
export type TargetKind    = NonNullable<AlertRuleTarget['kind']>;

// Manual — schema's polymorphic condition types resolve to `never`.
// Mirrors: cameleer-server-core RouteMetric, Comparator, AggregationOp enums
// and ExchangeMatchCondition.fireMode.
export type RouteMetric      = 'ERROR_RATE' | 'AVG_DURATION_MS' | 'P99_LATENCY_MS' | 'THROUGHPUT' | 'ERROR_COUNT';
export type Comparator       = 'GT' | 'GTE' | 'LT' | 'LTE' | 'EQ';
export type JvmAggregation   = 'MAX' | 'MIN' | 'AVG' | 'LATEST';
export type ExchangeFireMode = 'PER_EXCHANGE' | 'COUNT_IN_WINDOW';
export type AgentLifecycleEventType =
  | 'REGISTERED'
  | 'RE_REGISTERED'
  | 'DEREGISTERED'
  | 'WENT_STALE'
  | 'WENT_DEAD'
  | 'RECOVERED';

export interface Option<T extends string> { value: T; label: string }

/**
 * Build a dropdown option array from a `Record<T, string>` label map.
 *
 * Declaration order is load-bearing: ES2015+ guarantees `Object.keys` yields
 * string-keyed entries in insertion order, which is what the user sees.
 *
 * Hidden values stay in the `Record<T, string>` map (so `Record`-exhaustiveness
 * still pins labels to the union) and in the type `T` (so rules carrying a
 * hidden value round-trip fine through save/load) — they're simply filtered
 * out of the visible option array.
 */
function toOptions<T extends string>(labels: Record<T, string>, hidden?: readonly T[]): Option<T>[] {
  const skip: ReadonlySet<T> = new Set(hidden ?? []);
  return (Object.keys(labels) as T[])
    .filter((value) => !skip.has(value))
    .map((value) => ({ value, label: labels[value] }));
}

// ---------------------------------------------------------------------------
// Label maps — declaration order = dropdown order.
// ---------------------------------------------------------------------------

const CONDITION_KIND_LABELS: Record<ConditionKind, string> = {
  ROUTE_METRIC:     'Route metric (error rate, latency, throughput)',
  EXCHANGE_MATCH:   'Exchange match (specific failures)',
  AGENT_STATE:      'Agent state (DEAD / STALE)',
  AGENT_LIFECYCLE:  'Agent lifecycle (register / restart / stale / dead)',
  DEPLOYMENT_STATE: 'Deployment state (FAILED / DEGRADED)',
  LOG_PATTERN:      'Log pattern (count of matching logs)',
  JVM_METRIC:       'JVM metric (heap, GC, inflight)',
};

const SEVERITY_LABELS: Record<Severity, string> = {
  CRITICAL: 'Critical',
  WARNING:  'Warning',
  INFO:     'Info',
};

const ROUTE_METRIC_LABELS: Record<RouteMetric, string> = {
  ERROR_RATE:      'Error rate',
  P99_LATENCY_MS:  'P99 latency (ms)',
  AVG_DURATION_MS: 'Avg duration (ms)',
  THROUGHPUT:      'Throughput (msg/s)',
  ERROR_COUNT:     'Error count',
};

const COMPARATOR_LABELS: Record<Comparator, string> = {
  GT:  '>',
  GTE: '\u2265',
  LT:  '<',
  LTE: '\u2264',
  EQ:  '=',
};

// Previous UI ordering (MAX, AVG, MIN) preserved; LATEST is declared last
// and hidden — see comment on JVM_AGGREGATION_HIDDEN below.
const JVM_AGGREGATION_LABELS: Record<JvmAggregation, string> = {
  MAX:    'MAX',
  AVG:    'AVG',
  MIN:    'MIN',
  LATEST: 'LATEST',
};

const EXCHANGE_FIRE_MODE_LABELS: Record<ExchangeFireMode, string> = {
  PER_EXCHANGE:    'One alert per matching exchange',
  COUNT_IN_WINDOW: 'Threshold: N matches in window',
};

const AGENT_LIFECYCLE_EVENT_TYPE_LABELS: Record<AgentLifecycleEventType, string> = {
  WENT_STALE:    'Went stale (heartbeat missed)',
  WENT_DEAD:     'Went dead (extended silence)',
  RECOVERED:     'Recovered (stale → live)',
  REGISTERED:    'Registered (first check-in)',
  RE_REGISTERED: 'Re-registered (app restart)',
  DEREGISTERED:  'Deregistered (graceful shutdown)',
};

const TARGET_KIND_LABELS: Record<TargetKind, string> = {
  USER:  'User',
  GROUP: 'Group',
  ROLE:  'Role',
};

// ---------------------------------------------------------------------------
// Hidden values — legal on the wire (saved/loaded rules round-trip fine) but
// intentionally not surfaced in dropdowns. Document *why* — silent omission
// is exactly what caused the original drift problem.
// ---------------------------------------------------------------------------

const COMPARATOR_HIDDEN: readonly Comparator[] = [
  'EQ',  // Exact equality on floating-point metrics is rarely useful and noisy
         // — users should pick GT/LT with a sensible threshold instead.
];

const JVM_AGGREGATION_HIDDEN: readonly JvmAggregation[] = [
  'LATEST',  // Point-in-time reads belong on a metric dashboard, not an alert
             // rule — a windowed MAX/MIN/AVG is what you want for alerting.
];

// ---------------------------------------------------------------------------
// Exported option arrays (visible in dropdowns).
// ---------------------------------------------------------------------------

export const CONDITION_KIND_OPTIONS:     Option<ConditionKind>[]     = toOptions(CONDITION_KIND_LABELS);
export const SEVERITY_OPTIONS:           Option<Severity>[]          = toOptions(SEVERITY_LABELS);
export const ROUTE_METRIC_OPTIONS:       Option<RouteMetric>[]       = toOptions(ROUTE_METRIC_LABELS);
export const COMPARATOR_OPTIONS:         Option<Comparator>[]        = toOptions(COMPARATOR_LABELS, COMPARATOR_HIDDEN);
export const JVM_AGGREGATION_OPTIONS:    Option<JvmAggregation>[]    = toOptions(JVM_AGGREGATION_LABELS, JVM_AGGREGATION_HIDDEN);
export const EXCHANGE_FIRE_MODE_OPTIONS: Option<ExchangeFireMode>[]  = toOptions(EXCHANGE_FIRE_MODE_LABELS);
export const TARGET_KIND_OPTIONS:        Option<TargetKind>[]        = toOptions(TARGET_KIND_LABELS);
export const AGENT_LIFECYCLE_EVENT_TYPE_OPTIONS: Option<AgentLifecycleEventType>[] =
  toOptions(AGENT_LIFECYCLE_EVENT_TYPE_LABELS);
