/**
 * Le formulaire d'une règle : état, contrôle Zod côté écran, et charge utile
 * envoyée au serveur. Fonctions PURES, testées dans `rule-form.test.ts`.
 *
 * Le serveur (MessageAutomationRuleRequest) reste l'arbitre : ce schéma sert à
 * dire l'erreur AVANT l'aller-retour, avec les mêmes règles — décalage ≥ 1 jour
 * pour les déclencheurs qui le lisent, heure hors des heures calmes, au moins
 * un texte, aucune variable inconnue.
 *
 * `type` et non `interface` : `useForm` d'Inertia exige une signature d'index
 * implicite, que TypeScript n'infère que pour un alias (errors.md, 25/09/2026).
 */
import type { AutomationMode, AutomationSource, AutomationTrigger, PropertyScope, RuleRow, RuleTemplate } from '@/types/automatisations';
import { z } from 'zod';
import { fill, unknownTokens, usesOffset, usesSendTime } from './automatisations-format';

export type RuleFormData = {
    name: string;
    mode: AutomationMode;
    trigger: AutomationTrigger;
    /** Chaîne : un champ numérique vidé pendant la saisie ne doit pas redevenir « 0 » sous le curseur. */
    offset_days: string;
    send_time: string;
    delay_minutes: string;
    sources: AutomationSource[];
    property_scope: PropertyScope;
    property_ids: number[];
    bodies: { fr: string; en: string };
    email_subjects: { fr: string; en: string };
    catch_up: boolean;
};

/** Ce que le serveur reçoit (mêmes noms de champs que la Form Request). */
export type RulePayload = {
    name: string;
    mode: AutomationMode;
    trigger: AutomationTrigger;
    offset_days: number;
    send_time: string | null;
    delay_minutes: number;
    sources: AutomationSource[];
    property_scope: PropertyScope;
    property_ids: number[];
    bodies: { fr: string | null; en: string | null };
    email_subjects: { fr: string | null; en: string | null };
    catch_up: boolean;
};

/** Heures calmes (config message_automation.quiet_hours) : aucune heure d'envoi n'y est acceptée. */
export const SEND_TIME_MIN = '08:00';
/** Une heure avant les heures calmes : le planificateur ne passe qu'une fois par heure (OVH). */
export const SEND_TIME_MAX = '21:00';

export const BODY_MAX = 2000;
export const SUBJECT_MAX = 150;

/** Une règle neuve : « À valider », toutes provenances, tous les logements. */
export function emptyRuleForm(sources: AutomationSource[]): RuleFormData {
    return {
        name: '',
        mode: 'approval',
        trigger: 'before_checkin',
        offset_days: '1',
        send_time: '10:00',
        delay_minutes: '0',
        sources: [...sources],
        property_scope: 'all',
        property_ids: [],
        bodies: { fr: '', en: '' },
        email_subjects: { fr: '', en: '' },
        catch_up: true,
    };
}

export function ruleFormFromRow(rule: RuleRow): RuleFormData {
    return {
        name: rule.name,
        mode: rule.mode,
        trigger: rule.trigger,
        offset_days: String(usesOffset(rule.trigger) ? Math.max(1, rule.offsetDays) : rule.offsetDays),
        send_time: rule.sendTime ?? '10:00',
        delay_minutes: String(rule.delayMinutes),
        sources: [...rule.sources],
        property_scope: rule.propertyScope,
        property_ids: [...rule.propertyIds],
        bodies: { fr: rule.bodies.fr ?? '', en: rule.bodies.en ?? '' },
        email_subjects: { fr: rule.emailSubjects.fr ?? '', en: rule.emailSubjects.en ?? '' },
        catch_up: rule.catchUp,
    };
}

/** Longueur maximale d'un nom de règle (même borne que le serveur). */
export const NAME_MAX = 120;

/**
 * Une nouvelle règle pré-remplie par un modèle : ses textes, objets et moment
 * d'envoi, et pour le reste les réglages d'une règle neuve — « À valider »,
 * toutes provenances, tous les logements, rattrapage. Rien n'est enregistré :
 * la règle passe par « Enregistrer » comme toute autre.
 */
export function ruleFormFromTemplate(template: RuleTemplate, sources: AutomationSource[]): RuleFormData {
    const base = emptyRuleForm(sources);

    return {
        ...base,
        name: template.name.slice(0, NAME_MAX),
        mode: 'approval',
        trigger: template.trigger,
        offset_days: usesOffset(template.trigger) ? String(Math.max(1, template.offsetDays)) : base.offset_days,
        send_time: template.sendTime ?? base.send_time,
        delay_minutes: String(template.delayMinutes),
        bodies: { fr: template.bodies.fr, en: template.bodies.en },
        email_subjects: { fr: template.emailSubjects.fr, en: template.emailSubjects.en },
    };
}

/**
 * La copie d'une règle, à enregistrer comme une nouvelle : tout est repris,
 * sauf le nom (`copyLabel`, ex. « Copie de :name ») et le mode, ramené à
 * « À valider » — une copie ne doit pas partir seule sans qu'on l'ait choisi.
 */
export function duplicateRuleForm(rule: RuleRow, copyLabel: string): RuleFormData {
    return {
        ...ruleFormFromRow(rule),
        name: fill(copyLabel, { name: rule.name }).slice(0, NAME_MAX),
        mode: 'approval',
    };
}

function toInt(value: string): number {
    const parsed = Number.parseInt(value.trim(), 10);

    return Number.isFinite(parsed) ? parsed : 0;
}

function textOrNull(value: string): string | null {
    return value.trim() === '' ? null : value;
}

/** Seules les valeurs que le déclencheur lit partent : le serveur normalise de même. */
export function rulePayload(form: RuleFormData): RulePayload {
    return {
        name: form.name.trim(),
        mode: form.mode,
        trigger: form.trigger,
        offset_days: usesOffset(form.trigger) ? toInt(form.offset_days) : 0,
        send_time: usesSendTime(form.trigger) ? form.send_time : null,
        delay_minutes: form.trigger === 'booking_confirmed' ? toInt(form.delay_minutes) : 0,
        sources: [...form.sources],
        property_scope: form.property_scope,
        property_ids: form.property_scope === 'selected' ? [...form.property_ids] : [],
        bodies: { fr: textOrNull(form.bodies.fr), en: textOrNull(form.bodies.en) },
        email_subjects: { fr: textOrNull(form.email_subjects.fr), en: textOrNull(form.email_subjects.en) },
        catch_up: form.catch_up,
    };
}

/**
 * Messages d'erreur : une CLÉ de traduction (namespace `automatisations`),
 * éventuellement suivie de `|valeur` pour le remplacement `:token`.
 */
export const RULE_ERRORS = {
    nameRequired: 'validation_name_required',
    nameMax: 'validation_name_max',
    offsetMin: 'validation_offset_min',
    offsetMax: 'validation_offset_max',
    sendTimeFormat: 'validation_send_time_format',
    sendTimeQuiet: 'validation_send_time_quiet_short',
    delayRange: 'validation_delay_range',
    sourcesRequired: 'validation_sources_required',
    propertiesRequired: 'validation_properties_required',
    bodyRequired: 'validation_body_required',
    bodyMax: 'validation_body_max',
    subjectMax: 'validation_subject_max',
    unknownToken: 'validation_unknown_token',
} as const;

const TIME = /^([01]\d|2[0-3]):([0-5]\d)$/;
const INTEGER = /^\d+$/;

/**
 * Le schéma du formulaire. `forPreview` : le nom n'est pas exigé (l'aperçu
 * doit apparaître avant qu'on l'ait écrit — même exception côté serveur).
 */
export function buildRuleSchema(knownTokens: readonly string[], forPreview = false) {
    const localized = z.object({ fr: z.string(), en: z.string() });

    return z
        .object({
            name: z.string(),
            mode: z.enum(['approval', 'auto']),
            trigger: z.enum([
                'booking_confirmed',
                'before_checkin',
                'checkin_day',
                'during_stay',
                'before_checkout',
                'checkout_day',
                'after_checkout',
            ]),
            offset_days: z.string(),
            send_time: z.string(),
            delay_minutes: z.string(),
            sources: z.array(z.enum(['airbnb', 'booking', 'expedia', 'site', 'local', 'other'])),
            property_scope: z.enum(['all', 'selected']),
            property_ids: z.array(z.number().int()),
            bodies: localized,
            email_subjects: localized,
            catch_up: z.boolean(),
        })
        .superRefine((form, ctx) => {
            const issue = (path: (string | number)[], message: string) => ctx.addIssue({ code: z.ZodIssueCode.custom, path, message });

            if (!forPreview && form.name.trim() === '') issue(['name'], RULE_ERRORS.nameRequired);
            if (form.name.trim().length > 120) issue(['name'], RULE_ERRORS.nameMax);

            if (usesOffset(form.trigger)) {
                const offset = form.offset_days.trim();
                if (!INTEGER.test(offset) || Number(offset) < 1) issue(['offset_days'], RULE_ERRORS.offsetMin);
                else if (Number(offset) > 60) issue(['offset_days'], RULE_ERRORS.offsetMax);
            }

            if (usesSendTime(form.trigger)) {
                if (!TIME.test(form.send_time)) issue(['send_time'], RULE_ERRORS.sendTimeFormat);
                else if (form.send_time < SEND_TIME_MIN || form.send_time > SEND_TIME_MAX) issue(['send_time'], RULE_ERRORS.sendTimeQuiet);
            } else {
                const delay = form.delay_minutes.trim();
                if (!INTEGER.test(delay) || Number(delay) > 1440) issue(['delay_minutes'], RULE_ERRORS.delayRange);
            }

            if (form.sources.length === 0) issue(['sources'], RULE_ERRORS.sourcesRequired);
            if (form.property_scope === 'selected' && form.property_ids.length === 0) issue(['property_ids'], RULE_ERRORS.propertiesRequired);

            if (form.bodies.fr.trim() === '' && form.bodies.en.trim() === '') issue(['bodies'], RULE_ERRORS.bodyRequired);

            for (const lang of ['fr', 'en'] as const) {
                if (form.bodies[lang].length > BODY_MAX) issue(['bodies', lang], RULE_ERRORS.bodyMax);
                if (form.email_subjects[lang].length > SUBJECT_MAX) issue(['email_subjects', lang], RULE_ERRORS.subjectMax);

                for (const token of unknownTokens(form.bodies[lang], knownTokens)) {
                    issue(['bodies', lang], `${RULE_ERRORS.unknownToken}|{${token}}`);
                }
                for (const token of unknownTokens(form.email_subjects[lang], knownTokens)) {
                    issue(['email_subjects', lang], `${RULE_ERRORS.unknownToken}|{${token}}`);
                }
            }
        });
}

/**
 * Les erreurs du schéma, une par champ (la première), sous la forme des
 * erreurs Laravel : `{ 'bodies.fr': 'validation_unknown_token|{prénom}' }`.
 */
export function validateRuleForm(form: RuleFormData, knownTokens: readonly string[], forPreview = false): Record<string, string> {
    const result = buildRuleSchema(knownTokens, forPreview).safeParse(form);
    if (result.success) return {};

    const errors: Record<string, string> = {};
    for (const issue of result.error.issues) {
        const key = issue.path.join('.');
        if (!(key in errors)) errors[key] = issue.message;
    }

    return errors;
}
