/**
 * Yield Manager table row — one entry per logement (property), whether or not
 * it already has a yield config. Shape is provided by the backend
 * (YieldController index) per the frontend contract.
 */
export interface YieldTableRow {
    id: number | null; // yield config id; null if property has no config
    uid: string; // property identifier used by toggle/activate
    name: string;
    internal_name: string | null;
    is_active: boolean; // yield active (config exists AND active)
    has_config: boolean;
    base_price: number | null; // USD
    min_price: number | null;
    max_price: number | null;
    last_push_status: string | null;
    last_pushed_at: string | null;
    /** 0..100 — part des nuits VENDABLES des 7 prochains jours déjà vendues (même mesure que l'OBA, YieldOccupancyService) ; 0 sans nuit vendable. */
    occupancy_pct: number;
    rule_group_id: number | null; // linked rule group, if any
    rule_group_name: string | null; // its name (for the row badge)
    current_price: number | null;
    /** Une photo annuelle existe pour ce logement : le retour en arrière est possible. */
    has_backup: boolean; // TND
}

/**
 * One day in a price preview, as returned by the yield.preview endpoint.
 * Shared by the calendar preview and any consumer of the live preview stream.
 */
export interface PriceDay {
    date: string; // YYYY-MM-DD
    price: number; // USD
    is_booked: boolean;
    reason: string[];
}

/**
 * Per-property day series, keyed by property uid, as merged from the
 * yield.calendar batch endpoint (`{ series: { [uid]: PriceDay[] } }`).
 * A missing entry means "not yet fetched" (skeleton); an empty array means
 * "fetched, no data".
 */
export type YieldSeries = Record<string, PriceDay[]>;

/** Batch calendar endpoint response shape (POST yield.calendar). */
export interface YieldCalendarResponse {
    series: YieldSeries;
}

/**
 * One seasonal override in the date-range seasonality editor. `pct` is a PERCENT
 * adjustment (0 neutral, +45, -15) applied over the inclusive [start_date, end_date]
 * window. Dates are local ISO `YYYY-MM-DD`.
 *
 * Trois sortes de saisons (même contrat que App\Services\Yield\SeasonCalendar,
 * copié dans lib/season-calendar.ts) :
 *  - ponctuelle (aucun drapeau) : les dates, telles quelles ;
 *  - annuelle (`repeat: 'yearly'`) : seuls le jour et le mois comptent, la
 *    saison revient chaque année ;
 *  - fête mobile (`holiday` + `offset_start` / `offset_end`) : J+offset_start
 *    → J+offset_end autour de chaque date de la fête (config/yield-holidays.php) ;
 *    les dates n'y sont qu'un cache, affiché en lecture seule.
 * `layer: 'event'` : événement prioritaire, il l'emporte sur la saison de fond
 * qu'il recouvre.
 */
// `type` et non `interface` : un alias d'objet satisfait la signature d'index
// de FormDataType (useForm d'Inertia, YieldRuleEditor), une interface non.
export type YieldSeason = {
    name: string;
    start_date: string; // YYYY-MM-DD
    end_date: string; // YYYY-MM-DD
    pct: number; // percent: 0 neutral, +45, -15
    repeat?: 'yearly';
    layer?: SeasonLayer;
    holiday?: MovableHolidayKey;
    offset_start?: number;
    offset_end?: number;
};

/** Niveau de priorité d'une saison : l'événement passe avant la saison de fond. */
export type SeasonLayer = 'season' | 'event';

/** Fêtes à date mobile auxquelles une saison peut se rattacher (SeasonCalendar::MOVABLE_HOLIDAYS). */
export type MovableHolidayKey = 'eid_al_fitr' | 'eid_al_adha' | 'easter';

/**
 * Table des dates des fêtes mobiles (config/yield-holidays.php), servie par le
 * serveur (`movableHolidays`). `confirmed` : les dates confirmées par l'annonce
 * officielle — les autres sont estimées à ±1 jour.
 */
export type MovableHolidays = Record<string, { dates: string[]; confirmed?: string[] }>;

/**
 * The seasonality payload read/written by YieldRuleEditor and POSTed to
 * yield.preview / yield.update. `base_pct` is the percent applied to any date not
 * covered by a season.
 */
export type YieldSeasonality = {
    base_pct: number; // percent applied to uncovered dates
    seasons: YieldSeason[];
};

/** One last-minute discount tier: an unbooked night within `days_before` × multiplier. */
export interface LastMinuteRule {
    days_before: number;
    multiplier: number;
    label: string;
}

/**
 * Far-out premium: above `threshold_days` lead time, add `premium_pct`.
 *
 * `premium_pct` est une FRACTION stockée (0.10 = +10 %) — ne jamais l'afficher
 * brute : l'écran la saisit et la montre en % entier, via
 * farOutPremiumToField() / farOutPremiumFromField() (lib/rule-units.ts).
 */
export interface FarOutPremium {
    threshold_days: number;
    premium_pct: number;
}

/**
 * Un seuil OBA d'une échéance : dès que `occ` % des nuits VENDABLES de
 * l'échéance sont vendues (séjours propriétaires, demandes et blocages exclus),
 * le prix est multiplié par `mult`. La première ligne est toujours 0 %, et
 * `mult` vaut au moins 1 : le remplissage ne fait que monter les prix
 * (lib/oba-rules.ts).
 */
export interface ObaThreshold {
    occ: number;
    mult: number;
}

/** One OBA window [fromDay, toDay] (lead-time days) carrying its occupancy thresholds. */
export interface ObaRule {
    window: [number, number];
    thresholds: ObaThreshold[];
}

/**
 * Règle « week-end » : `multiplier` s'applique aux nuits dont le jour ISO est
 * dans `days` (1 = lundi … 7 = dimanche ; la nuit du vendredi est celle qui
 * COMMENCE le vendredi). ×1 = aucun effet — c'est le défaut d'un logement qui
 * ne l'a jamais réglée. Toujours lue via normalizeWeekend() (lib/rule-sections.ts).
 */
export interface WeekendRule {
    multiplier: number;
    days: number[];
}

/** The five rule sections a group can carry (each nullable = "not part of this group"). */
export type RuleSectionKey = 'seasonality' | 'last_minute_rules' | 'far_out_premium' | 'oba_rules' | 'weekend_rule';

/**
 * Les cinq sections de règles d'un logement, toutes renseignées. Sert aux
 * valeurs que le serveur fournit à l'éditeur : `sectionDefaults` (rallumer une
 * section) et `sectionDisabled` (la couper : forme NEUTRE, ×1,00 partout).
 */
export interface RuleSectionValues {
    seasonality: YieldSeasonality;
    last_minute_rules: LastMinuteRule[];
    far_out_premium: FarOutPremium;
    oba_rules: ObaRule[];
    weekend_rule: WeekendRule;
}

/** Choix de stratégie OBA à l'activation : 'none' = « Aucune » (OBA désactivé). */
export type ObaPresetChoice = 'none' | 'conservative' | 'recommended' | 'aggressive';

/**
 * A reusable, tenant-scoped rule group. Each section is null when the group does
 * not include it. `sections` lists the included (non-null) section keys; the
 * frontend uses it to render which parts apply. `linked_count` is how many
 * logements currently follow this group.
 */
export interface YieldRuleGroup {
    id: number;
    name: string;
    description: string | null;
    sections: RuleSectionKey[];
    seasonality: YieldSeasonality | null;
    last_minute_rules: LastMinuteRule[] | null;
    far_out_premium: FarOutPremium | null;
    oba_rules: ObaRule[] | null;
    weekend_rule: WeekendRule | null;
    linked_count: number;
}

/**
 * ═══════════════════════════════════════════════════════════════════════════
 * ENVOI MANUEL VERS RENTALS UNITED (« Push maintenant »)
 * ═══════════════════════════════════════════════════════════════════════════
 * Contrat figé avec le lot serveur. Les montants sont ceux de Rentals United,
 * en devise BRUTE (USD) — jamais convertis en TND.
 */

/** Une nuit du comparatif : ce que Rentals United porte aujourd'hui vs ce que le yield propose. */
export interface PushPreviewNight {
    date: string; // YYYY-MM-DD
    /** Prix actuel chez Rentals United ; `null` quand la nuit n'en porte aucun. */
    current: number | null;
    proposed: number;
    /** Écart en pourcentage ; `null` quand il n'y a pas de prix actuel à comparer. */
    delta_pct: number | null;
    /** Une nuit réservée n'est PAS modifiée par l'envoi. */
    is_booked: boolean;
}

/** L'en-tête du comparatif, lisible d'un coup d'œil. */
export interface PushPreviewSummary {
    /** Nuits réellement envoyées (les réservées sont exclues). */
    count: number;
    booked_skipped: number;
    min_proposed: number | null;
    max_proposed: number | null;
    avg_delta_pct: number | null;
    currency: string;
    nights_without_current_price: number;
}

/**
 * Ce que ferait « Revenir aux prix d'avant », calculé par le serveur avec le
 * MÊME plan que la restauration (YieldPriceRestoreService::plan) :
 *  - `restore`        des nuits de la photo seront réécrites ;
 *  - `no_origin`      la photo ne porte aucun prix pour la fenêtre (photo vide) ;
 *  - `nothing_pushed` le yield n'a rien écrit depuis la photo ;
 *  - `window_past`    tout ce que le yield a écrit est déjà passé.
 * Toutes les issues éteignent le yield et effacent la photo ; seule `restore` écrit.
 */
export type RestoreOutcome = 'restore' | 'no_origin' | 'nothing_pushed' | 'window_past';

/** État de la photo annuelle des prix — ce qui rend l'envoi réversible. */
export interface PushBackupState {
    exists: boolean;
    captured_at: string | null; // ISO 8601
    nights: number;
    /** Issue d'une restauration ; `null` sans photo ou sur un corps incomplet. */
    restore_outcome: RestoreOutcome | null;
    restore_from: string | null; // YYYY-MM-DD
    restore_until: string | null; // YYYY-MM-DD — dernière nuit écrite par le yield
    /** Nuits de la photo qui seraient réécrites. */
    restore_nights: number;
    /** Nuits de la fenêtre sans prix d'origine : elles gardent le dernier prix du yield. */
    restore_without_origin: number;
}

/** Corps de POST yield.push.preview. */
export interface PushPreviewResponse {
    nights: PushPreviewNight[];
    summary: PushPreviewSummary;
    backup: PushBackupState;
}

/** Une franchise proposée par le sélecteur du compte principal (`id` = son rentals_subuser_id). */
export interface YieldFranchiseOption {
    id: string;
    name: string;
}

/**
 * Le périmètre de l'écran, rendu par YieldTenantScope::yieldScopeProps() sur
 * chaque page du Yield Manager.
 *
 *  - `coHosts` : les franchises consultables — NON VIDE au compte principal
 *    seulement (une franchise ne voit jamais le sélecteur) ;
 *  - `coHost` : la franchise consultée (`?coHost=`), null hors consultation ;
 *  - `readOnly` : le compte principal consulte une franchise — le serveur
 *    refuse alors TOUTE écriture (403), l'écran éteint donc ses contrôles.
 */
export interface YieldScope {
    coHost: string | null;
    coHosts: YieldFranchiseOption[];
    readOnly: boolean;
}
