local M = {}

-- Phase 3W-8C: customer Quick/Pro Core Films — canonical public profiles only (keep in sync with AnalogSignatureCoreMenu.lua).
local CORE_EMULSION_IDS = {
    portra_160_accurate_prototype = true,
    portra_400_accurate_prototype = true,
    portra_800_accurate_prototype = true,
    gold_200 = true,
    colorplus_200 = true,
    ultramax_400 = true,
    ektar_100 = true,
    fuji_400h_accurate_prototype = true,
    fuji_superia_400 = true,
    fuji_velvia_50 = true,
    fuji_provia_100f = true,
    ektachrome_accurate_prototype = true,
    kodachrome_64_accurate_prototype = true,
    vision3_50d = true,
    vision3_250d = true,
    vision3_500t = true,
    cinestill_50d = true,
    cinestill_800t = true,
    bw_hp5 = true,
    bw_delta_100 = true,
    bw_trix_400 = true,
    bw_tmax_400 = true,
    bw_foma_100 = true,
    bw_foma_400 = true,
}

M.defaultCoreFilmId = "portra_400_accurate_prototype"

M.filmGroupOrder = {
    "core",
    "color_negative",
    "slide",
    "bw",
    "cine",
    "creative",
}

M.sceneItems = {
    { title = "Portrait", value = "POR", legacy = "por" },
    { title = "Wedding", value = "ZG", legacy = "zg" },
    { title = "Editorial", value = "FA", legacy = "fa" },
    { title = "Travel", value = "KRA", legacy = "kra" },
    { title = "Night", value = "NOC", legacy = "noc" },
    { title = "Product", value = "PRD", legacy = "fa" },
    { title = "Street", value = "REP", legacy = "rep" },
    { title = "Vintage", value = "VIN", legacy = "vin" },
    { title = "Experimental", value = "EXP", legacy = "exp" },
}

M.colorProcessItems = {
    { title = "C-41", value = "c41" },
    { title = "E-6", value = "e6" },
    { title = "K-14", value = "k14" },
    { title = "Black & White", value = "bw" },
    { title = "Cineon Log", value = "cineon_log" },
}

M.printFinishItems = {
    { title = "Neutral", value = "neutral" },
    { title = "Print Contrast", value = "print_contrast" },
    { title = "Matte Fade", value = "matte_fade" },
    { title = "Warm Lab", value = "warm_lab" },
}

local function lower(value)
    return string.lower(tostring(value or ""))
end

local function contains(blob, needle)
    return string.find(blob, needle, 1, true) ~= nil
end

local function groupForEmulsion(emulsion)
    local id = lower(emulsion and emulsion.id)
    local label = lower((emulsion and emulsion.clientFacingLabel) or (emulsion and emulsion.label))
    local blob = id .. " " .. label
    if CORE_EMULSION_IDS[id] then
        return "core"
    end
    if emulsion and emulsion.bw == true then
        return "bw"
    end
    if contains(blob, "trix") or contains(blob, "hp5") or contains(blob, "tmax")
        or contains(blob, "delta") or contains(blob, "foma") or contains(blob, "bw_")
    then
        return "bw"
    end
    if contains(blob, "velvia") or contains(blob, "provia") or contains(blob, "ektachrome")
        or contains(blob, "kodachrome") or contains(blob, "astia")
    then
        return "slide"
    end
    if contains(blob, "vision3") or contains(blob, "cinestill") or contains(blob, "cine")
        or contains(blob, "eterna")
    then
        return "cine"
    end
    if contains(blob, "lomo") or contains(blob, "redscale") or contains(blob, "expired")
        or contains(blob, "experimental") or contains(blob, "implosion") or contains(blob, "phenomena")
    then
        return "creative"
    end
    return "color_negative"
end

function M.filmGroupItems(labels)
    labels = labels or {}
    return {
        { title = labels.core or "Core Films", value = "core" },
        { title = labels.color_negative or "Color Negative", value = "color_negative" },
        { title = labels.slide or "Slide / Reversal", value = "slide" },
        { title = labels.bw or "Black & White", value = "bw" },
        { title = labels.cine or "Motion Picture / Cine", value = "cine" },
        { title = labels.creative or "Creative / Experimental", value = "creative" },
    }
end

function M.filmItemsForGroup(config, groupId)
    local grouped = {}
    for _, row in ipairs((config and config.emulsions) or {}) do
        if groupForEmulsion(row) == tostring(groupId or "core") then
            grouped[#grouped + 1] = row
        end
    end
    table.sort(grouped, function(a, b)
        local la = tostring(a.clientFacingLabel or a.publicLabel or a.label or a.id)
        local lb = tostring(b.clientFacingLabel or b.publicLabel or b.label or b.id)
        return la < lb
    end)
    local items = {}
    for _, row in ipairs(grouped) do
        items[#items + 1] = {
            title = tostring(row.clientFacingLabel or row.publicLabel or row.label or row.id),
            value = tostring(row.id),
        }
    end
    return items
end

function M.ensureFilmId(config, filmId)
    local wanted = tostring(filmId or "")
    if wanted ~= "" and config and type(config.findEmulsion) == "function" and config.findEmulsion(wanted) then
        return wanted
    end
    for _, groupId in ipairs(M.filmGroupOrder) do
        local items = M.filmItemsForGroup(config, groupId)
        if #items > 0 then
            return tostring(items[1].value)
        end
    end
    return M.defaultCoreFilmId or "portra_400_accurate_prototype"
end

function M.formatItems(config)
    local items = {}
    for _, row in ipairs((config and config.formats) or {}) do
        items[#items + 1] = { title = tostring(row.label or row.id), value = tostring(row.id) }
    end
    return items
end

function M.baseFamilyItems(config)
    local items = {}
    for _, row in ipairs((config and config.baseFamilies) or {}) do
        items[#items + 1] = { title = tostring(row.label or row.id), value = tostring(row.id) }
    end
    return items
end

local function pct(value)
    local n = tonumber(value) or 0
    if n < 0 then n = 0 end
    if n > 100 then n = 100 end
    return n / 100
end

local function signedPct(value)
    local n = tonumber(value) or 0
    if n < -100 then n = -100 end
    if n > 100 then n = 100 end
    return n / 100
end

local function sceneLegacy(scene)
    local wanted = tostring(scene or "REP")
    for _, item in ipairs(M.sceneItems) do
        if tostring(item.value) == wanted then
            return tostring(item.legacy)
        end
    end
    return "rep"
end

local function boolText(value)
    return (value == true) and "true" or "false"
end

local function q(value)
    return "\"" .. tostring(value or "") .. "\""
end

local function numberText(value)
    return string.format("%.2f", tonumber(value) or 0)
end

local function bandFor(value)
    local n = tonumber(value) or 0
    if n <= 0 then
        return "off"
    elseif n < 0.35 then
        return "low"
    elseif n < 0.70 then
        return "medium"
    end
    return "high"
end

local function level3(value, lowValue, midValue, highValue)
    local band = bandFor(value)
    if band == "off" then
        return "base"
    elseif band == "low" then
        return lowValue
    elseif band == "medium" then
        return midValue
    end
    return highValue
end

local QUICK_MAPPING_ORDER = {
    "film_group",
    "film_id",
    "scene",
    "legacy_workflow",
    "strength",
    "smart_film_match",
    "skin_safety",
    "highlight_protection",
    "grain_amount",
}

local PRO_MAPPING_ORDER = {
    "film_group",
    "film_id",
    "film_format",
    "base_family",
    "scene",
    "legacy_workflow",
    "smart_film_match",
    "wb_exposure_match",
    "tone",
    "color_process",
    "highlight_protection",
    "shadow_density",
    "saturation_trim",
    "grain_amount",
    "grain_size",
    "grain_texture",
    "shadow_grain",
    "highlight_grain",
    "skin_safety",
    "halation_feel",
    "bloom",
    "print_finish",
}

M.legacyExpectations = {
    quick_v2 = {
        film_group = {
            target_panel = "Panel I",
            target_concept = "film selector grouping only",
            status = "OK",
            confidence = "medium",
            notes = "Grouping is UI-only. The legacy runtime consumes film_id, not the group label.",
        },
        film_id = {
            target_panel = "Panel I",
            target_concept = "emulsion/internal film key",
            status = "OK",
            confidence = "high",
            notes = "Uses the stable FilmEngineConfig emulsion id.",
        },
        scene = {
            target_panel = "Panel I",
            target_concept = "workflow display code",
            status = "OK",
            confidence = "high",
            notes = "Display code is stable and maps to legacy_workflow.",
        },
        legacy_workflow = {
            target_panel = "Panel I",
            target_concept = "smartMatchWorkflow code",
            status = "OK",
            confidence = "high",
            notes = "Lowercase workflow code mirrors the legacy Panel I runtime expectation.",
        },
        strength = {
            target_panel = "Panel I",
            target_concept = "LUT intensity / global film strength intent",
            status = "OK",
            confidence = "medium",
            notes = "Normalized 0..1 intent. Legacy UI may render as 0..100 before runtime normalization.",
        },
        smart_film_match = {
            target_panel = "Panel I",
            target_concept = "smartMatch boolean",
            status = "OK",
            confidence = "high",
            notes = "Boolean intent maps directly to the legacy smart match concept.",
        },
        skin_safety = {
            target_panel = "Panel I / Panel II",
            target_concept = "skin-protective matching intent",
            status = "Intent OK / Runtime Needs Verification",
            confidence = "medium",
            notes = "No single verified legacy scalar exists. Treated as a high-level safety guard until runtime comparison.",
        },
        highlight_protection = {
            target_panel = "Panel II",
            target_concept = "softHighs / whiteClip intent",
            status = "OK",
            confidence = "medium",
            notes = "Read-only intent maps to Panel II highlight controls: softHighs plus a smaller whiteClip guard.",
        },
        grain_amount = {
            target_panel = "Panel IV",
            target_concept = "SSG grain level intent",
            status = "OK",
            confidence = "medium",
            notes = "Read-only intent maps to documented Panel IV grain levels: ssg_grain, grain_rms, grain_clumping, crystal_size, and roughness.",
        },
    },
    pro_v2 = {
        film_group = {
            target_panel = "Panel I",
            target_concept = "film selector grouping only",
            status = "OK",
            confidence = "medium",
            notes = "Grouping is UI-only. The legacy runtime consumes film_id, not the group label.",
        },
        film_id = {
            target_panel = "Panel I",
            target_concept = "emulsion/internal film key",
            status = "OK",
            confidence = "high",
            notes = "Uses the stable FilmEngineConfig emulsion id.",
        },
        film_format = {
            target_panel = "Panel I",
            target_concept = "formatId",
            status = "OK",
            confidence = "high",
            notes = "Uses FilmEngineConfig format ids.",
        },
        base_family = {
            target_panel = "Panel I / Panel II",
            target_concept = "baseFamily",
            status = "OK",
            confidence = "high",
            notes = "Uses FilmEngineConfig base family ids.",
        },
        scene = {
            target_panel = "Panel I",
            target_concept = "workflow display code",
            status = "OK",
            confidence = "high",
            notes = "Display code is stable and maps to legacy_workflow.",
        },
        legacy_workflow = {
            target_panel = "Panel I",
            target_concept = "smartMatchWorkflow code",
            status = "OK",
            confidence = "high",
            notes = "Lowercase workflow code mirrors the legacy Panel I runtime expectation.",
        },
        smart_film_match = {
            target_panel = "Panel I",
            target_concept = "smartMatch boolean",
            status = "OK",
            confidence = "high",
            notes = "Boolean intent maps directly to the legacy smart match concept.",
        },
        wb_exposure_match = {
            target_panel = "Panel I",
            target_concept = "smartMatchAmount / analyzer intent",
            status = "Needs Mapping",
            confidence = "medium",
            notes = "Expected to influence smart match amount and WB/exposure normalization. Exact runtime scale is not verified.",
        },
        tone = {
            target_panel = "Panel II",
            target_concept = "labFade / contrast tone intent",
            status = "Needs Mapping",
            confidence = "low",
            notes = "Tone is a high-level intent and does not yet map to one verified legacy scalar.",
        },
        color_process = {
            target_panel = "Panel II",
            target_concept = "colorProcess",
            status = "OK",
            confidence = "high",
            notes = "Uses normalized legacy process ids: c41, e6, k14, bw, cineon_log.",
        },
        highlight_protection = {
            target_panel = "Panel II",
            target_concept = "softHighs / whiteClip intent",
            status = "Needs Mapping",
            confidence = "medium",
            notes = "Expected to affect Panel II softHighs / whiteClip. Exact scalar mapping is not verified.",
        },
        shadow_density = {
            target_panel = "Panel II",
            target_concept = "softLows / blackClip intent",
            status = "Needs Mapping",
            confidence = "medium",
            notes = "Expected to affect shadow density through Panel II low-end controls. Exact scalar mapping is not verified.",
        },
        saturation_trim = {
            target_panel = "Panel II",
            target_concept = "saturationTrim",
            status = "Needs Mapping",
            confidence = "medium",
            notes = "Concept matches Panel II saturationTrim, but sandbox uses normalized -1..1 and the exact legacy UI scale needs confirmation.",
        },
        grain_amount = {
            target_panel = "Panel IV",
            target_concept = "SSG grain amount intent",
            status = "Needs Mapping",
            confidence = "medium",
            notes = "Expected to affect Panel IV SSG grain intent. Exact scalar mapping is not verified.",
        },
        grain_size = {
            target_panel = "Panel IV",
            target_concept = "crystal size / resolving loss intent",
            status = "Needs Mapping",
            confidence = "medium",
            notes = "Expected to influence Panel IV crystal/detail behavior. Exact scalar mapping is not verified.",
        },
        grain_texture = {
            target_panel = "Panel IV",
            target_concept = "roughness / clumping texture intent",
            status = "Needs Mapping",
            confidence = "medium",
            notes = "Expected to influence clumping or roughness. Exact scalar mapping is not verified.",
        },
        shadow_grain = {
            target_panel = "Panel IV",
            target_concept = "toe grain intent",
            status = "Needs Mapping",
            confidence = "medium",
            notes = "Expected to affect shadow-region grain. Exact toe grain mapping is not verified.",
        },
        highlight_grain = {
            target_panel = "Panel IV",
            target_concept = "shoulder grain intent",
            status = "Needs Mapping",
            confidence = "medium",
            notes = "Expected to affect highlight-region grain. Exact shoulder grain mapping is not verified.",
        },
        skin_safety = {
            target_panel = "Panel I / Panel II",
            target_concept = "skin-protective matching intent",
            status = "Intent OK / Runtime Needs Verification",
            confidence = "medium",
            notes = "Legacy behavior is distributed across smart match and tone/color safeguards; no single verified scalar yet.",
        },
        halation_feel = {
            target_panel = "Panel V",
            target_concept = "anti-halation / optical bloom intent",
            status = "Needs Mapping",
            confidence = "low",
            notes = "Concept matches optics/halation controls, but exact legacy mapping is not verified.",
        },
        bloom = {
            target_panel = "Panel V",
            target_concept = "optical bloom intent",
            status = "Needs Mapping",
            confidence = "low",
            notes = "Concept matches optics bloom controls, but exact legacy mapping is not verified.",
        },
        print_finish = {
            target_panel = "Panel VI",
            target_concept = "print finish / legacy print intent",
            status = "Needs Mapping",
            confidence = "low",
            notes = "Print finish is a simplified intent. Exact Panel VI mapping is not verified.",
        },
    },
}

local function mappingOrder(mode)
    return (mode == "pro_v2") and PRO_MAPPING_ORDER or QUICK_MAPPING_ORDER
end

local function buildMappingAudit(mode)
    local expectations = M.legacyExpectations[mode] or {}
    local check = {}
    local confidence = {}
    local notes = {}
    local summary = { OK = 0, ["Intent OK / Runtime Needs Verification"] = 0, ["Needs Mapping"] = 0, Unknown = 0 }

    for _, key in ipairs(mappingOrder(mode)) do
        local entry = expectations[key]
        local status = (entry and entry.status) or "Unknown"
        check[key] = status
        confidence[key] = (entry and entry.confidence) or "low"
        notes[key] = (entry and entry.notes) or "No legacy expectation has been documented for this field yet."
        if status == "OK" then
            summary.OK = summary.OK + 1
        elseif status == "Intent OK / Runtime Needs Verification" then
            summary["Intent OK / Runtime Needs Verification"] = summary["Intent OK / Runtime Needs Verification"] + 1
        elseif status == "Needs Mapping" then
            summary["Needs Mapping"] = summary["Needs Mapping"] + 1
        else
            summary.Unknown = summary.Unknown + 1
        end
    end

    return check, confidence, notes, summary
end

local function buildSkinSafetyMapping(props, payload)
    local enabled = (props.skinSafety == true)
    local legacyWorkflow = sceneLegacy(props.scene)
    return {
        target_panel = "Panel I / Panel II",
        target_concept = "portrait-safe smart match and tone/color protection",
        status = "Intent OK / Runtime Needs Verification",
        confidence = "medium",
        input = enabled,
        mapped_intent = {
            smart_match_guard = true,
            saturation_aggression_guard = true,
            orange_red_hue_protection = true,
            highlight_softening_guard = true,
            avoid_skin_hue_drift = true,
        },
        notes = "No single verified legacy scalar exists. Treated as a high-level safety guard until runtime comparison.",
    }
end

local function buildHighlightProtectionMapping(value)
    local input = pct(value)
    return {
        target_panel = "Panel II",
        target_concept = "softHighs / whiteClip / highlight roll-off",
        status = "OK",
        confidence = "medium",
        input = input,
        mapped_intent = {
            soft_highs = input,
            soft_highs_percent = math.floor((input * 100) + 0.5),
            white_clip_guard = math.min(0.35, input * 0.50),
            white_clip_guard_percent = math.floor((math.min(0.35, input * 0.50) * 100) + 0.5),
            highlight_rolloff_intent = bandFor(input),
        },
        notes = "Read-only diagnostic mapping. Panel II applies softHighs as shoulder compression and whiteClip as a stronger white guard.",
    }
end

local function buildGrainMapping(value)
    local input = pct(value)
    return {
        target_panel = "Panel IV",
        target_concept = "SSG grain level family",
        status = "OK",
        confidence = "medium",
        input = input,
        mapped_intent = {
            ssg_grain = level3(input, "refined", "balanced", "raw"),
            grain_rms = level3(input, "fine", "neutral", "bold"),
            grain_clumping = level3(input, "smooth", "organic", "broken"),
            crystal_size = level3(input, "tight", "neutral", "large"),
            roughness = level3(input, "silk", "natural", "coarse"),
            grain_intensity_band = bandFor(input),
        },
        notes = "Read-only diagnostic mapping based on documented Panel IV grain level families. No grain values are written.",
    }
end

function M.quickPayload(props)
    local check, confidence, notes, summary = buildMappingAudit("quick_v2")
    local payload = {
        sandbox_mode = true,
        ui_mode = "quick_v2",
        read_only = true,
        film_group = tostring(props.filmGroup or "core"),
        film_id = tostring(props.filmId or M.defaultCoreFilmId),
        scene = tostring(props.scene or "REP"),
        legacy_workflow = sceneLegacy(props.scene),
        strength = pct(props.strength),
        smart_film_match = (props.smartFilmMatch == true),
        skin_safety = (props.skinSafety == true),
        highlight_protection = pct(props.highlightProtection),
        grain_amount = pct(props.grainAmount),
        forbidden_writes_checked = true,
        legacy_prefs_touched = false,
        develop_writes_touched = false,
        legacy_panels_executed = false,
        engine_executed = false,
        catalog_touched = false,
        legacy_mapping_check = check,
        legacy_mapping_confidence = confidence,
        legacy_mapping_notes = notes,
        legacy_mapping_summary = summary,
    }
    payload.skin_safety_mapping = buildSkinSafetyMapping(props, payload)
    payload.highlight_protection_mapping = buildHighlightProtectionMapping(props.highlightProtection)
    payload.grain_mapping = buildGrainMapping(props.grainAmount)
    return payload
end

function M.proPayload(props)
    local check, confidence, notes, summary = buildMappingAudit("pro_v2")
    return {
        sandbox_mode = true,
        ui_mode = "pro_v2",
        read_only = true,
        film_group = tostring(props.filmGroup or "core"),
        film_id = tostring(props.filmId or M.defaultCoreFilmId),
        film_format = tostring(props.filmFormat or "35mm"),
        base_family = tostring(props.baseFamily or "neutral_cmos"),
        scene = tostring(props.scene or "REP"),
        legacy_workflow = sceneLegacy(props.scene),
        smart_film_match = (props.smartFilmMatch == true),
        wb_exposure_match = pct(props.wbExposure),
        tone = signedPct(props.tone),
        color_process = tostring(props.colorProcess or "c41"),
        highlight_protection = pct(props.highlightProtection),
        shadow_density = pct(props.shadowDensity),
        saturation_trim = signedPct(props.saturationTrim),
        grain_amount = pct(props.grainAmount),
        grain_size = pct(props.grainSize),
        grain_texture = pct(props.grainTexture),
        shadow_grain = pct(props.shadowGrain),
        highlight_grain = pct(props.highlightGrain),
        skin_safety = (props.skinSafety == true),
        halation_feel = pct(props.halationFeel),
        bloom = pct(props.bloom),
        print_finish = tostring(props.printFinish or "neutral"),
        forbidden_writes_checked = true,
        legacy_prefs_touched = false,
        develop_writes_touched = false,
        legacy_panels_executed = false,
        engine_executed = false,
        catalog_touched = false,
        legacy_mapping_check = check,
        legacy_mapping_confidence = confidence,
        legacy_mapping_notes = notes,
        legacy_mapping_summary = summary,
    }
end

local QUICK_ORDER = {
    "sandbox_mode", "ui_mode", "read_only", "film_group", "film_id", "scene",
    "legacy_workflow", "strength", "smart_film_match", "skin_safety",
    "highlight_protection", "grain_amount", "forbidden_writes_checked",
    "legacy_prefs_touched", "develop_writes_touched", "legacy_panels_executed",
    "engine_executed", "catalog_touched",
}

local PRO_ORDER = {
    "sandbox_mode", "ui_mode", "read_only", "film_group", "film_id", "film_format",
    "base_family", "scene", "legacy_workflow", "smart_film_match",
    "wb_exposure_match", "tone", "color_process", "highlight_protection",
    "shadow_density", "saturation_trim", "grain_amount", "grain_size",
    "grain_texture", "shadow_grain", "highlight_grain", "skin_safety",
    "halation_feel", "bloom", "print_finish", "forbidden_writes_checked",
    "legacy_prefs_touched", "develop_writes_touched", "legacy_panels_executed",
    "engine_executed", "catalog_touched",
}

local function renderScalar(value)
    if type(value) == "boolean" then
        return boolText(value)
    elseif type(value) == "number" then
        return numberText(value)
    end
    return q(value)
end

local function appendMappingTable(lines, name, payload, valueKey)
    local mode = tostring(payload.ui_mode or "quick_v2")
    local values = payload[name] or {}
    lines[#lines + 1] = "  " .. name .. " = {"
    local order = mappingOrder(mode)
    for index, key in ipairs(order) do
        local suffix = (index < #order) and "," or ""
        lines[#lines + 1] = "    " .. key .. " = " .. q(values[key]) .. suffix
    end
    lines[#lines + 1] = "  }" .. (valueKey and "," or "")
end

local function appendSummaryTable(lines, payload)
    local summary = payload.legacy_mapping_summary or {}
    lines[#lines + 1] = "  legacy_mapping_summary = {"
    lines[#lines + 1] = "    ok = " .. tostring(summary.OK or 0) .. ","
    lines[#lines + 1] = "    intent_ok_runtime_needs_verification = " .. tostring(summary["Intent OK / Runtime Needs Verification"] or 0) .. ","
    lines[#lines + 1] = "    needs_mapping = " .. tostring(summary["Needs Mapping"] or 0) .. ","
    lines[#lines + 1] = "    unknown = " .. tostring(summary.Unknown or 0)
    lines[#lines + 1] = "  },"
end

local function appendNestedObject(lines, indent, values, order)
    local pad = string.rep(" ", indent)
    for index, entry in ipairs(order or {}) do
        local key = entry[1]
        local value = values and values[key]
        local suffix = (index < #order) and "," or ""
        if type(entry[2]) == "table" then
            lines[#lines + 1] = pad .. tostring(key) .. " = {"
            appendNestedObject(lines, indent + 2, value or {}, entry[2])
            lines[#lines + 1] = pad .. "}" .. suffix
        else
            lines[#lines + 1] = pad .. tostring(key) .. " = " .. renderScalar(value) .. suffix
        end
    end
end

local QUICK_RESOLUTION_OBJECTS = {
    {
        name = "skin_safety_mapping",
        order = {
            { "target_panel" },
            { "target_concept" },
            { "status" },
            { "confidence" },
            { "input" },
            { "mapped_intent", {
                { "smart_match_guard" },
                { "saturation_aggression_guard" },
                { "orange_red_hue_protection" },
                { "highlight_softening_guard" },
                { "avoid_skin_hue_drift" },
            } },
            { "notes" },
        },
    },
    {
        name = "highlight_protection_mapping",
        order = {
            { "target_panel" },
            { "target_concept" },
            { "status" },
            { "confidence" },
            { "input" },
            { "mapped_intent", {
                { "soft_highs" },
                { "soft_highs_percent" },
                { "white_clip_guard" },
                { "white_clip_guard_percent" },
                { "highlight_rolloff_intent" },
            } },
            { "notes" },
        },
    },
    {
        name = "grain_mapping",
        order = {
            { "target_panel" },
            { "target_concept" },
            { "status" },
            { "confidence" },
            { "input" },
            { "mapped_intent", {
                { "ssg_grain" },
                { "grain_rms" },
                { "grain_clumping" },
                { "crystal_size" },
                { "roughness" },
                { "grain_intensity_band" },
            } },
            { "notes" },
        },
    },
}

local function appendQuickResolutionObjects(lines, payload)
    if payload.ui_mode ~= "quick_v2" then
        return
    end
    for _, object in ipairs(QUICK_RESOLUTION_OBJECTS) do
        lines[#lines + 1] = "  " .. object.name .. " = {"
        appendNestedObject(lines, 4, payload[object.name] or {}, object.order)
        lines[#lines + 1] = "  },"
    end
end

function M.payloadText(payload)
    local order = (payload.ui_mode == "pro_v2") and PRO_ORDER or QUICK_ORDER
    local lines = { "{" }
    for index, key in ipairs(order) do
        local value = payload[key]
        lines[#lines + 1] = "  " .. tostring(key) .. " = " .. renderScalar(value) .. ","
    end
    appendSummaryTable(lines, payload)
    appendQuickResolutionObjects(lines, payload)
    appendMappingTable(lines, "legacy_mapping_check", payload, true)
    appendMappingTable(lines, "legacy_mapping_confidence", payload, true)
    appendMappingTable(lines, "legacy_mapping_notes", payload, false)
    lines[#lines + 1] = "}"
    return table.concat(lines, "\n")
end

function M.expectationLines(payload, labels)
    labels = labels or {}
    local ok = labels.ok or "OK"
    local needs = labels.needs or "Needs mapping"
    local unknown = labels.unknown or "Unknown / not implemented"
    local intent_ok = labels.intent_ok or "Intent OK / Runtime Needs Verification"
    local statusLabels = { OK = ok, ["Intent OK / Runtime Needs Verification"] = intent_ok, ["Needs Mapping"] = needs, Unknown = unknown }
    local expectations = M.legacyExpectations[tostring(payload.ui_mode or "quick_v2")] or {}
    local lines = {}
    for _, key in ipairs(mappingOrder(tostring(payload.ui_mode or "quick_v2"))) do
        local entry = expectations[key] or {}
        local status = (payload.legacy_mapping_check and payload.legacy_mapping_check[key]) or entry.status or "Unknown"
        local confidence = (payload.legacy_mapping_confidence and payload.legacy_mapping_confidence[key]) or entry.confidence or "low"
        lines[#lines + 1] = tostring(key)
            .. " -> "
            .. tostring(entry.target_panel or "Unknown")
            .. " / "
            .. tostring(entry.target_concept or "unmapped")
            .. ": "
            .. (statusLabels[status] or unknown)
            .. " ("
            .. confidence
            .. ")"
    end
    return table.concat(lines, "\n")
end

function M.mappingSummaryText(payload, labels)
    labels = labels or {}
    local summary = payload.legacy_mapping_summary or {}
    local ok = labels.ok or "OK"
    local intent_ok = labels.intent_ok or "Intent OK / Runtime Needs Verification"
    local needs = labels.needs or "Needs mapping"
    local unknown = labels.unknown or "Unknown / not implemented"
    return ok .. ": " .. tostring(summary.OK or 0)
        .. " | " .. intent_ok .. ": " .. tostring(summary["Intent OK / Runtime Needs Verification"] or 0)
        .. " | " .. needs .. ": " .. tostring(summary["Needs Mapping"] or 0)
        .. " | " .. unknown .. ": " .. tostring(summary.Unknown or 0)
end

function M.quickPreviewGate(payload)
    return {
        allowed = false,
        reason = "skin_safety is intent-mapped but not runtime verified; no visual/runtime comparison has been performed",
        blockers = {
            "skin_safety runtime equivalence test",
            "no visual/runtime comparison yet",
            "no virtual-copy preview isolation implemented",
        },
    }
end

local function appendQuickIntentReport(lines, payload)
    lines[#lines + 1] = "1. Quick UI Intent"
    lines[#lines + 1] = "{"
    for index, key in ipairs({
        "film_group",
        "film_id",
        "scene",
        "legacy_workflow",
        "strength",
        "smart_film_match",
        "skin_safety",
        "highlight_protection",
        "grain_amount",
    }) do
        local suffix = (index < 9) and "," or ""
        lines[#lines + 1] = "  " .. key .. " = " .. renderScalar(payload[key]) .. suffix
    end
    lines[#lines + 1] = "}"
end

local function appendExpectedLegacyReport(lines, payload)
    local highlight = payload.highlight_protection_mapping or {}
    local highlightIntent = highlight.mapped_intent or {}
    local skin = payload.skin_safety_mapping or {}
    local skinIntent = skin.mapped_intent or {}
    local grain = payload.grain_mapping or {}
    local grainIntent = grain.mapped_intent or {}

    lines[#lines + 1] = ""
    lines[#lines + 1] = "2. Expected Legacy Equivalent"
    lines[#lines + 1] = ""
    lines[#lines + 1] = "Panel I — Ingest & Calibration"
    lines[#lines + 1] = "- film_id -> emulsion/internal film key: " .. q(payload.film_id)
    lines[#lines + 1] = "- legacy_workflow -> workflow profile/code: " .. q(payload.legacy_workflow)
    lines[#lines + 1] = "- strength -> analog/smart match strength intent: " .. renderScalar(payload.strength)
    lines[#lines + 1] = "- smart_film_match -> match/analyze intent: " .. renderScalar(payload.smart_film_match)
    lines[#lines + 1] = ""
    lines[#lines + 1] = "Panel II — Tonality & Lab"
    lines[#lines + 1] = "- highlight_protection -> softHighs: " .. renderScalar(highlightIntent.soft_highs)
        .. ", whiteClip guard: " .. renderScalar(highlightIntent.white_clip_guard)
        .. ", roll-off: " .. q(highlightIntent.highlight_rolloff_intent)
    lines[#lines + 1] = "- skin_safety -> portrait-safe tonal/color guard: " .. tostring(skin.status or "Needs Mapping")
    lines[#lines + 1] = ""
    lines[#lines + 1] = "Panel IV — SSG / Detail Structure"
    lines[#lines + 1] = "- grain_amount -> SSG grain family: " .. q(grainIntent.ssg_grain)
        .. ", RMS: " .. q(grainIntent.grain_rms)
        .. ", clumping: " .. q(grainIntent.grain_clumping)
        .. ", crystal: " .. q(grainIntent.crystal_size)
        .. ", roughness: " .. q(grainIntent.roughness)
end

local function appendMappingStatusReport(lines, payload)
    local expectations = M.legacyExpectations.quick_v2 or {}
    lines[#lines + 1] = ""
    lines[#lines + 1] = "3. Mapping Status Table"
    lines[#lines + 1] = "field | target panel | target concept | status | confidence | notes"
    lines[#lines + 1] = "--- | --- | --- | --- | --- | ---"
    for _, key in ipairs(QUICK_MAPPING_ORDER) do
        local entry = expectations[key] or {}
        local status = (payload.legacy_mapping_check and payload.legacy_mapping_check[key]) or entry.status or "Unknown"
        local confidence = (payload.legacy_mapping_confidence and payload.legacy_mapping_confidence[key]) or entry.confidence or "low"
        local notes = (payload.legacy_mapping_notes and payload.legacy_mapping_notes[key]) or entry.notes or ""
        lines[#lines + 1] = key
            .. " | "
            .. tostring(entry.target_panel or "Unknown")
            .. " | "
            .. tostring(entry.target_concept or "unmapped")
            .. " | "
            .. tostring(status)
            .. " | "
            .. tostring(confidence)
            .. " | "
            .. tostring(notes)
    end
end

local function appendPreviewGateReport(lines, payload)
    local gate = M.quickPreviewGate(payload)
    lines[#lines + 1] = ""
    lines[#lines + 1] = "4. Critical Gate Summary"
    lines[#lines + 1] = "quick_v2_preview_gate = {"
    lines[#lines + 1] = "  allowed = " .. renderScalar(gate.allowed) .. ","
    lines[#lines + 1] = "  reason = " .. q(gate.reason) .. ","
    lines[#lines + 1] = "  blockers = {"
    for index, blocker in ipairs(gate.blockers or {}) do
        local suffix = (index < #(gate.blockers or {})) and "," or ""
        lines[#lines + 1] = "    " .. q(blocker) .. suffix
    end
    lines[#lines + 1] = "  }"
    lines[#lines + 1] = "}"
end

function M.quickComparisonReport(payload)
    payload = payload or M.quickPayload({})
    local lines = {
        "Quick v2 Legacy Payload Comparison Report",
        "Read-only diagnostic report. No image changes, Develop writes, catalog writes, prefs writes, engine execution, or legacy panel execution.",
        "",
        "Mapping Summary: " .. M.mappingSummaryText(payload),
        "",
    }
    appendQuickIntentReport(lines, payload)
    appendExpectedLegacyReport(lines, payload)
    appendMappingStatusReport(lines, payload)
    appendPreviewGateReport(lines, payload)
    return table.concat(lines, "\n")
end

return M
