/**
 * Invalidation protocol — header formatting, auto-scoping.
 *
 * Matches Django's implementation exactly. Same format. Same rules.
 */

import type { RegistryEntry } from './types'
import { getContextGroups, getContextParamNames, getFunction } from './registry'

type InvalidateEntry = string | { context: string; params: Record<string, any> }

/**
 * Resolve invalidation targets with three-tier auto-scoping.
 *
 * Tier 1: Argument name matching
 * Tier 2: Auth inference (Edge-side, not handled here)
 * Tier 3: Broad fallback
 */
export function resolveInvalidation(
    entry: RegistryEntry,
    callArgs: Record<string, any> | null,
): InvalidateEntry[] | null {
    if (!entry.affects) return null

    const result: InvalidateEntry[] = []
    const seen = new Set<string>()

    for (const target of entry.affects) {
        const targetName = target.name
        if (seen.has(targetName)) continue
        seen.add(targetName)

        // Resolve which context the target belongs to (for param lookup)
        const resolved = resolveAffectsTarget(targetName)
        const ctxForParams = resolved.type === 'function' ? resolved.context : resolved.name

        // Tier 1: argument name matching
        if (callArgs && ctxForParams) {
            const contextParams = getContextParamNames(ctxForParams)
            const matched: Record<string, any> = {}
            for (const [k, v] of Object.entries(callArgs)) {
                if (contextParams.has(k)) matched[k] = v
            }
            if (Object.keys(matched).length > 0) {
                result.push({ context: targetName, params: matched })
                continue
            }
        }

        // Tier 3: broad fallback
        result.push(targetName)
    }

    return result.length > 0 ? result : null
}

/**
 * Determine whether an affects target is a context name or function name.
 */
function resolveAffectsTarget(name: string): { type: 'context' | 'function'; name: string; context?: string } {
    const groups = getContextGroups()

    if (name in groups) {
        return { type: 'context', name }
    }

    for (const [ctxName, fnNames] of Object.entries(groups)) {
        if (fnNames.includes(name)) {
            return { type: 'function', name, context: ctxName }
        }
    }

    return { type: 'context', name }
}

/**
 * Format invalidation targets as X-Mizan-Invalidate header value.
 *
 * Format: comma-separated contexts. Semicolon-separated URL-encoded params.
 */
export function formatInvalidateHeader(invalidate: InvalidateEntry[]): string {
    const parts: string[] = []

    for (const entry of invalidate) {
        if (typeof entry === 'string') {
            parts.push(entry)
        } else {
            const { context, params } = entry
            if (params && Object.keys(params).length > 0) {
                const paramStr = Object.entries(params)
                    .sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)
                    .map(([k, v]) => `${encodeURIComponent(String(k))}=${encodeURIComponent(String(v))}`)
                    .join(';')
                parts.push(`${context};${paramStr}`)
            } else {
                parts.push(context)
            }
        }
    }

    return parts.join(', ')
}
