/**
 * Schemas Hydra / API Platform 4.
 *
 * Important : API Platform 4 abandonne le prefixe `hydra:` dans les noms de
 * proprietes (compare a la version 3). Un GET /api/audit-logs renvoie :
 *   { "@context": ..., "@id": ..., "@type": "...",
 *     "member": [...],
 *     "totalItems": 30,
 *     "view": { "@id": ..., "@type": "...", "first": ..., "next": ..., ... } }
 *
 * En `application/json` (sans ld), API Platform retourne un simple tableau
 * plat sans ces metadonnees — on doit donc explicitement demander
 * `application/ld+json` (via l'option `headers: { Accept: ... }` de useApi)
 * pour avoir acces a la pagination.
 */
export interface HydraView {
    '@id'?: string
    '@type'?: string
    first?: string
    last?: string
    next?: string
    previous?: string
}

export interface HydraCollection<T> {
    member: T[]
    totalItems: number
    view?: HydraView
}

export function extractHydraMembers<T>(collection: HydraCollection<T>): T[] {
    return collection.member ?? []
}

/**
 * Une violation de contrainte API Platform (reponse 422). Le `propertyPath`
 * pointe le champ concerne, `message` est le libelle a afficher, `code` est le
 * code de contrainte Symfony (UUID stable, independant de la langue) — il sert
 * a surcharger un message back technique par une cle i18n (cf.
 * `VIOLATION_MESSAGE_I18N` / `resolveViolationMessage`).
 */
export interface ApiViolation {
    propertyPath: string
    message: string
    code: string
}

/**
 * Extrait les violations d'un payload d'erreur 422 d'API Platform 4. Supporte
 * les deux formats de negociation (`violations` ou `hydra:violations`) et
 * renvoie un tableau vide si le payload n'en contient pas d'exploitables.
 *
 * Utilise par useCategoryForm et tout futur composable de formulaire qui
 * doit mapper les violations serveur sur ses champs.
 */
export function extractApiViolations(data: unknown): ApiViolation[] {
    if (!data || typeof data !== 'object') return []
    const record = data as Record<string, unknown>
    const raw = record.violations ?? record['hydra:violations']
    if (!Array.isArray(raw)) return []
    const out: ApiViolation[] = []
    for (const v of raw) {
        if (!v || typeof v !== 'object') continue
        const obj = v as Record<string, unknown>
        out.push({
            propertyPath: String(obj.propertyPath ?? ''),
            message: String(obj.message ?? ''),
            code: String(obj.code ?? ''),
        })
    }
    return out
}

/**
 * Transforme un payload d'erreur 422 d'API Platform en dictionnaire
 * `{ propertyPath: message }`, directement consommable par la prop `:error`
 * des composants `Malio*` (le nom du champ cote front = le `propertyPath`
 * renvoye par le back). Fondation du mapping erreur→champ des formulaires :
 * utilise par `useFormErrors` (champs scalaires) et par les boucles de submit
 * de collections (erreur par ligne).
 *
 * Les violations sans `propertyPath` (erreur globale) sont ignorees ; en cas
 * de doublon de `propertyPath`, la derniere violation l'emporte.
 */
export function mapViolationsToRecord(data: unknown): Record<string, string> {
    const out: Record<string, string> = {}
    for (const v of extractApiViolations(data)) {
        if (v.propertyPath) out[v.propertyPath] = v.message
    }
    return out
}

/**
 * Surcharge i18n d'un message back par CODE de violation.
 *
 * La plupart des contraintes back portent deja un message FR explicite (ex.
 * `#[Assert\NotBlank(message: '...')]`) : on l'affiche tel quel. Mais certaines
 * 422 portent un message TECHNIQUE non montrable a l'utilisateur — typiquement
 * l'erreur de TYPE renvoyee par API Platform quand le back ne peut pas
 * denormaliser la valeur (date non parsable envoyee sur un champ
 * `DateTimeImmutable` : « Cette valeur doit être de type DateTimeImmutable|null. »,
 * voire en anglais selon la negociation de langue).
 *
 * Plutot que de traduire/maquiller cote back, on surcharge ces messages cote
 * front via leur `code` de violation. Ce code est un UUID Symfony FIGE (contrat
 * de compatibilite : il ne change pas entre versions), donc bien plus robuste
 * qu'un match sur le texte du message (qui depend de la langue). La table
 * associe un code -> une cle i18n ; `resolveViolationMessage` l'applique.
 *
 * Limite a connaitre : le code de type-error est GENERIQUE (toute valeur de
 * mauvais type). Dans nos formulaires, seul un champ date saisi en texte libre
 * (MalioDate, qui forwarde la saisie brute invalide) le declenche, d'ou le
 * libelle « Date invalide ». Si un autre champ typé en saisie libre apparait,
 * affiner la resolution via `propertyPath` plutot que par code seul.
 */
export const VIOLATION_MESSAGE_I18N: Record<string, string> = {
    // Symfony `Constraints\Type::INVALID_TYPE_ERROR` — valeur de mauvais type.
    'ba785a8c-82cb-4283-967c-3cf342181b40': 'errors.validation.invalidDate',
}

/**
 * Resout le message a afficher pour une violation : si son `code` est surcharge
 * par `VIOLATION_MESSAGE_I18N`, renvoie la traduction de la cle associee ;
 * sinon, le message back tel quel (cas nominal). `t` est passe par l'appelant
 * (les utils sont purs, sans acces a useI18n).
 */
export function resolveViolationMessage(v: ApiViolation, t: (key: string) => string): string {
    const i18nKey = VIOLATION_MESSAGE_I18N[v.code]
    return i18nKey ? t(i18nKey) : v.message
}

/**
 * Extrait un message d'erreur lisible depuis un payload Hydra / JSON
 * d'erreur API Platform. Essaie les champs courants dans l'ordre :
 * `hydra:description` → `detail` → `description` → `message` → `error` →
 * `title` → `hydra:title`. Renvoie '' si rien d'exploitable.
 *
 * Si `data` est une string, la renvoie telle quelle (cas des erreurs
 * Symfony en text/plain ou des messages bruts).
 */
export function extractApiErrorMessage(data: unknown): string {
    if (typeof data === 'string') return data
    if (!data || typeof data !== 'object') return ''
    const record = data as Record<string, unknown>
    return (
        (record['hydra:description'] as string)
        ?? (record.detail as string)
        ?? (record.description as string)
        ?? (record.message as string)
        ?? (record.error as string)
        ?? (record.title as string)
        ?? (record['hydra:title'] as string)
        ?? ''
    )
}
