/**
 * « Pièces pour le ménage » — ce que l'étape calcule sans le serveur.
 *
 * Les pièces sont celles de la fiche : le serveur en tire la STRUCTURE de la
 * check-list (groupes et étapes du catalogue, pièce par pièce). Ce que le
 * bureau règle par dessus — étape masquée, photo exigée ou retirée, étape
 * ajoutée — se lit ici directement dans le formulaire, pour qu'un geste se
 * voie tout de suite.
 */

export type Locale = 'fr' | 'ar' | 'en';
export type I18n = Partial<Record<Locale, string>>;

export interface CustomStep {
    id: string;
    group: string;
    label: string;
    details: string | null;
    photo: boolean;
}

/** Les réglages du bureau, seuls à s'enregistrer. */
export interface CleaningSettings {
    hidden: string[];
    photos: Record<string, boolean>;
    custom: CustomStep[];
}

export interface PlanStep {
    ref: string;
    step_key: string;
    label: I18n;
    details: I18n | null;
    photo: boolean;
    photo_default: boolean;
    hidden: boolean;
    custom: string | null;
}

export interface PlanGroup {
    key: string;
    code: string;
    kind: string;
    label: I18n;
    steps: PlanStep[];
}

/** Une pièce de la fiche, en lecture seule. */
export interface RoomCount {
    kind: string;
    label: I18n;
    count: number;
}

/** Une ligne telle que l'étape l'affiche. */
export interface StepRow {
    ref: string;
    label: I18n;
    details: I18n | null;
    photo: boolean;
    hidden: boolean;
    /** L'id d'une étape ajoutée par le bureau ; null pour une étape du catalogue. */
    custom: string | null;
}

/** Le texte dans la langue du CRM, le français sinon. */
export function pick(text: I18n | null | undefined, locale: string): string {
    if (!text) return '';
    return text[locale as Locale] ?? text.fr ?? '';
}

/**
 * Les réglages tels que le serveur les sert. PHP rend un tableau vide pour
 * `photos` sans entrée : il redevient un objet.
 */
export function readSettings(raw: Partial<CleaningSettings> | null | undefined): CleaningSettings {
    const photos = raw?.photos && !Array.isArray(raw.photos) ? raw.photos : {};
    return { hidden: raw?.hidden ?? [], photos, custom: raw?.custom ?? [] };
}

/** Les lignes d'un groupe : ses étapes du catalogue, réglées par le formulaire, puis les ajoutées. */
export function groupRows(group: PlanGroup, settings: CleaningSettings): StepRow[] {
    const rows: StepRow[] = group.steps
        .filter((step) => step.custom === null)
        .map((step) => ({
            ref: step.ref,
            label: step.label,
            details: step.details,
            photo: settings.photos[step.ref] ?? step.photo_default,
            hidden: settings.hidden.includes(step.ref),
            custom: null,
        }));

    for (const step of settings.custom) {
        if (step.group !== group.key) continue;
        rows.push({
            ref: `custom:${step.id}`,
            label: { fr: step.label },
            details: step.details ? { fr: step.details } : null,
            photo: step.photo,
            hidden: false,
            custom: step.id,
        });
    }

    return rows;
}

/** Le nombre d'étapes qu'un ménage recevra, et combien exigent une photo. */
export function countSteps(groups: PlanGroup[], settings: CleaningSettings): { steps: number; photos: number } {
    let steps = 0;
    let photos = 0;
    for (const group of groups) {
        for (const row of groupRows(group, settings)) {
            if (row.hidden) continue;
            steps++;
            if (row.photo) photos++;
        }
    }
    return { steps, photos };
}

/** Les étapes ajoutées dans une pièce qui n'existe plus : elles partiront à l'enregistrement. */
export function droppedCustom(groups: PlanGroup[], settings: CleaningSettings): CustomStep[] {
    const keys = new Set(groups.map((group) => group.key));
    return settings.custom.filter((step) => !keys.has(step.group));
}

/** Masquer, ou rétablir, une étape du catalogue. */
export function toggleHidden(settings: CleaningSettings, ref: string): CleaningSettings {
    const hidden = settings.hidden.includes(ref) ? settings.hidden.filter((r) => r !== ref) : [...settings.hidden, ref];
    return { ...settings, hidden };
}

/**
 * Exiger ou retirer la photo d'une étape. Pour une étape du catalogue, on ne
 * garde que l'écart à son réglage par défaut.
 */
export function togglePhoto(settings: CleaningSettings, row: StepRow, photoDefault: boolean): CleaningSettings {
    if (row.custom !== null) {
        return {
            ...settings,
            custom: settings.custom.map((step) => (step.id === row.custom ? { ...step, photo: !step.photo } : step)),
        };
    }

    const next = !row.photo;
    const photos = { ...settings.photos };
    if (next === photoDefault) {
        delete photos[row.ref];
    } else {
        photos[row.ref] = next;
    }
    return { ...settings, photos };
}

/** La check-list commune : rien de masqué, aucune photo changée, rien d'ajouté. */
export function sharedSettings(): CleaningSettings {
    return { hidden: [], photos: {}, custom: [] };
}

/** Un identifiant d'étape ajoutée : 8 caractères [a-z0-9], unique dans le logement. */
export function newCustomId(taken: string[], random: () => number = Math.random): string {
    const alphabet = 'abcdefghijklmnopqrstuvwxyz0123456789';
    for (;;) {
        let id = '';
        for (let i = 0; i < 8; i++) id += alphabet[Math.floor(random() * alphabet.length)];
        if (!taken.includes(id)) return id;
    }
}

/** Le formulaire diffère-t-il de ce qui est enregistré ? */
export function sameSettings(a: CleaningSettings, b: CleaningSettings): boolean {
    const canonical = (settings: CleaningSettings) =>
        JSON.stringify({
            hidden: [...settings.hidden].sort(),
            photos: Object.keys(settings.photos)
                .sort()
                .map((ref) => [ref, settings.photos[ref]]),
            custom: settings.custom.map((step) => [step.id, step.group, step.label, step.details ?? null, step.photo]),
        });
    return canonical(a) === canonical(b);
}

/** Ouvrir ou refermer le détail d'une étape (l'accordéon de la check-list). */
export function toggleOpen(open: ReadonlySet<string>, ref: string): Set<string> {
    const next = new Set(open);
    if (next.has(ref)) {
        next.delete(ref);
    } else {
        next.add(ref);
    }
    return next;
}
