/**
 * Public shape of an admin operator, as returned by `GET /admin/auth/me`
 * and `POST /admin/auth/login` — never includes the password hash. Mirrors
 * apps/api's `Omit<PrismaAdminUser, "passwordHash" | "tokenVersion">`,
 * declared by hand rather than derived from the Prisma type (same reason as
 * {@link SafeUserProfile}: apps/admin-web must not depend on
 * `@prisma/client`). `createdAt`/`lastLoginAt` are ISO 8601 strings (JSON
 * has no date type).
 */
export interface AdminUserView {
  id: number;
  email: string;
  name: string;
  createdAt: string;
  lastLoginAt: string | null;
}

/** One `{ key, count }` breakdown row — e.g. recipes per source, corrections per status. */
export interface MetricsBreakdownRow {
  key: string;
  /** Human-readable label when the key isn't self-explanatory (a source's display name); otherwise equal to `key`. */
  label: string;
  count: number;
}

/**
 * Point-in-time totals for the admin dashboard's KPI tiles — every field is
 * a `COUNT` against the current database, not a time series. See
 * `apps/api`'s `admin-metrics.service.ts`.
 */
export interface MetricsSnapshotView {
  admins: number;
  users: number;
  households: number;
  /** Households with at least one `Planning` row. */
  activeHouseholds: number;
  recipes: number;
  recipesManual: number;
  recipesImported: number;
  /** Imported recipes grouped by their `Source` (`key` = source key, `label` = display name). */
  recipesBySource: MetricsBreakdownRow[];
  plannings: number;
  planningItems: number;
  steps: number;
  detectedTechniques: number;
  favorites: number;
  corrections: number;
  /** Corrections not yet turned into a `TechStepTrainingSuggestion` (`consumedAt IS NULL`). */
  correctionsUnconsumed: number;
  /** Corrections that assert "no technique here" (`correctedTechStepId IS NULL`) — never become suggestions. */
  correctionsRemoval: number;
  trainingSuggestions: number;
  trainingSuggestionsByStatus: MetricsBreakdownRow[];
  trainingSuggestionsBySourceType: MetricsBreakdownRow[];
}

/** One day of a metrics time series. `date` is `YYYY-MM-DD` (UTC day). Days with no activity are present with `count: 0`. */
export interface MetricsTimeBucket {
  date: string;
  count: number;
}

/** One instrumented `AnalyticsEvent` type, bucketed by day over the requested window. */
export interface MetricsEventSeries {
  type: string;
  buckets: MetricsTimeBucket[];
}

/**
 * Response of `GET /admin/metrics`. `snapshot` is "right now"; `series` and
 * `events` cover the last `rangeDays` days as daily buckets (zero-filled).
 * `series` is derived from the `createdAt` columns added by the
 * `admin_metrics` migration; `events` from the `AnalyticsEvent` table.
 */
export interface MetricsView {
  generatedAt: string;
  rangeDays: number;
  snapshot: MetricsSnapshotView;
  series: {
    signups: MetricsTimeBucket[];
    recipesCreated: MetricsTimeBucket[];
    planningItemsAdded: MetricsTimeBucket[];
    correctionsSubmitted: MetricsTimeBucket[];
    trainingSuggestions: MetricsTimeBucket[];
  };
  events: MetricsEventSeries[];
}

/**
 * Health of one thing the admin monitoring board watches:
 * - `"up"` — reachable and healthy.
 * - `"degraded"` — reachable but not fully OK (e.g. the worker's last heartbeat
 *   is old, or a job it ran failed).
 * - `"down"` — unreachable / erroring.
 * - `"unknown"` — never observed (e.g. the worker has never sent a heartbeat).
 */
export type ServiceStatus = "up" | "degraded" | "down" | "unknown";

/** One row of `GET /admin/monitoring` — a single probed target. */
export interface ServiceHealthView {
  /** Stable id — `"postgres"`, `"api"`, `"intent-service"`, `"tech-step-llm-worker"`. Resolved to a label client-side via `admin.monitoring.service.<key>`. */
  key: string;
  status: ServiceStatus;
  /** Round-trip of the probe in ms, or `null` when there was nothing to time (the worker, read from a stored heartbeat). */
  latencyMs: number | null;
  /** Short human-readable extra (`"HTTP 200"`, `"uptime 3h 12m"`, `"dernier battement il y a 9 j"`). */
  detail: string | null;
  checkedAt: string;
  /** Worker only — when it last actually ran a job, and that job's summary. */
  lastRunAt?: string | null;
  lastResult?: { job?: string; ok?: boolean; counts?: Record<string, number> } | null;
}

/** Response of `GET /admin/monitoring` — an actively-probed status board. */
export interface MonitoringView {
  generatedAt: string;
  services: ServiceHealthView[];
}

/**
 * One `TechStepTrainingSuggestion` as shown in the admin triage UI, with
 * its source context resolved. `sourceCorrection` is present only when
 * `sourceType === "correction"` — for `"llm_audit"` the suggestion carries
 * no persisted clause context today (a known gap), just the synonyms/
 * utterances.
 */
export interface TrainingSuggestionAdminView {
  id: number;
  techStepKey: string;
  locale: string;
  suggestedSynonyms: string[];
  suggestedUtterances: string[];
  sourceType: string;
  status: string;
  createdAt: string;
  sourceCorrection: {
    id: number;
    recipeId: number;
    stepId: number;
    clauseText: string;
    previousTechStepKey: string | null;
    correctedTechStepKey: string | null;
  } | null;
}

/** Suggestions grouped by their technique key — the shape `GET /admin/tech-steps/suggestions` returns. */
export interface TrainingSuggestionGroupView {
  techStepKey: string;
  suggestions: TrainingSuggestionAdminView[];
}

/**
 * One raw `StepTechStepCorrection` for the admin corrections browser —
 * includes the "no technique here" removals (`correctedTechStepKey: null`)
 * that never become suggestions and are invisible to every other tool.
 */
export interface CorrectionAdminView {
  id: number;
  recipeId: number;
  stepId: number;
  stepDescription: string;
  clauseText: string;
  start: number;
  end: number;
  previousTechStepKey: string | null;
  correctedTechStepKey: string | null;
  createdAt: string;
  consumedAt: string | null;
}

/** Response of `GET /admin/tech-steps/training-data-snippet` — a ready-to-paste block. */
export interface TrainingDataSnippetView {
  techStepKey: string;
  locale: string;
  status: string;
  /** Number of suggestions aggregated into the snippet. */
  suggestionCount: number;
  synonyms: string[];
  utterances: string[];
  /** The `synonyms`/`utterances` rendered as a Python literal block for `training_data.py`. */
  snippet: string;
}

/**
 * Result of `POST /admin/tech-steps/retrain`. `gatePassed` is the F1
 * regression gate (`f1 >= minF1`); `backfilled` is present only when the
 * gate passed (a failed gate refuses to backfill). `marked` echoes how
 * many suggestion ids were actually flipped.
 */
export interface RetrainResultView {
  f1: number;
  precision: number;
  recall: number;
  minF1: number;
  gatePassed: boolean;
  backfilled: { total: number; changed: number } | null;
  marked: { applied: number; rejected: number };
}

/**
 * One row of `GET /admin/catalog/placeholders` — every placeholder
 * `Ingredient` (see `Ingredient.isPlaceholder` in schema.prisma) sharing
 * one normalized name, so a maintainer sees "this ingredient is missing
 * from the catalog, and N recipes are waiting on it" rather than a flat
 * list of near-duplicate one-off rows.
 *
 * `normalizedName` is the grouping key (lower-cased, accent-stripped,
 * whitespace-collapsed — see `apps/api`'s `admin-catalog.service.ts`).
 * `displayNames` is every distinct raw spelling that collapsed into it
 * ("Piment d'Espelette", "piment d espelette"). `ingredientIds` is every
 * placeholder row in the group — the payload `mark-reviewed` takes back.
 */
export interface CatalogPlaceholderGroupView {
  normalizedName: string;
  displayNames: string[];
  ingredientIds: number[];
  /** Distinct recipes that use at least one placeholder in this group. */
  recipeCount: number;
  /** Up to 5 of those recipes, for a "seen in…" preview. */
  sampleRecipes: { id: number; name: string }[];
  /** Earliest `createdAt` across the group's rows (ISO 8601), or `null` if none carry one. */
  firstSeenAt: string | null;
  /** `true` once every row in the group has been marked reviewed — the group then leaves the default working list. */
  allReviewed: boolean;
}

/** Result of `POST /admin/catalog/placeholders/prune-orphans` — how many placeholder rows with zero remaining recipe references were deleted. */
export interface PruneOrphansResultView {
  deleted: number;
}
