-- lib/LabCameraProfilePolicy.lua
-- Phase 3F-BLOCKER-C4/C4A/C4B/C4C/C6: Lab-only CameraProfile persistence prototype policy.

local LrFileUtils = import "LrFileUtils"
local LrPathUtils = import "LrPathUtils"

local M = {}

M.LAB_CAMERAPROFILE_ALLOWLIST = {
    ektachrome_accurate_prototype = true,
    kodachrome_64_accurate_prototype = true,
    kodachrome_v3 = true,
    portra_400_skinsafe_prototype = true,
}

-- Phase 3T-LR4: Creative [C] emulsions that require CameraProfile/DCP apply outside Lab track.
M.CREATIVE_CAMERAPROFILE_ALLOWLIST = {
}

M.LAB_PROFILE_CHECKBOX_PROP = "useAnalogLabCameraProfile"

local function trim(value)
    return (tostring(value or ""):gsub("^%s+", ""):gsub("%s+$", ""))
end

local function triStateBool(value)
    if value == true then
        return "true"
    end
    if value == false then
        return "false"
    end
    return "unknown"
end

function M.isLabCameraProfileEligible(emulsionId)
    local id = trim(emulsionId)
    return M.LAB_CAMERAPROFILE_ALLOWLIST[id] == true
end

function M.isCreativeCameraProfileEligible(emulsionId)
    local id = trim(emulsionId)
    return M.CREATIVE_CAMERAPROFILE_ALLOWLIST[id] == true
end

function M.isCameraProfileApplyEligible(emulsionId)
    local id = trim(emulsionId)
    return M.LAB_CAMERAPROFILE_ALLOWLIST[id] == true
        or M.CREATIVE_CAMERAPROFILE_ALLOWLIST[id] == true
end

local function pluginRoot(context)
    if type(context) == "table" and type(context.pluginPath) == "string" and context.pluginPath ~= "" then
        return context.pluginPath
    end
    if type(_PLUGIN) == "table" and type(_PLUGIN.path) == "string" and _PLUGIN.path ~= "" then
        return _PLUGIN.path
    end
    return nil
end

local dcpRuntimeGateModule = nil
local function getDcpRuntimeGate(context)
    if dcpRuntimeGateModule then
        return dcpRuntimeGateModule
    end
    local pluginPath = pluginRoot(context)
    if not pluginPath then
        return nil
    end
    local ok, loaded = pcall(dofile, LrPathUtils.child(pluginPath, "lib/DcpRuntimePersistenceGate.lua"))
    if ok and type(loaded) == "table" then
        dcpRuntimeGateModule = loaded
        return loaded
    end
    return nil
end

function M.defaultCheckboxForEmulsion(emulsionId)
    if not M.isCameraProfileApplyEligible(emulsionId) then
        return false
    end
    local gate = getDcpRuntimeGate({})
    if gate and type(gate.isRuntimeDcpDisabledForEmulsion) == "function" then
        if gate.isRuntimeDcpDisabledForEmulsion(emulsionId) then
            return false
        end
    elseif gate and (gate.isRuntimeDcpDisabled and gate.isRuntimeDcpDisabled() or gate.isRuntimeDcpPersistenceDisabled and gate.isRuntimeDcpPersistenceDisabled()) then
        return false
    end
    return true
end

function M.isRuntimeDcpGateActive(context, emulsionId)
    local gate = getDcpRuntimeGate(context or {})
    if gate and type(gate.isRuntimeDcpDisabledForEmulsion) == "function" then
        return gate.isRuntimeDcpDisabledForEmulsion(emulsionId) == true
    end
    if not gate then
        return false
    end
    if type(gate.isRuntimeDcpDisabled) == "function" then
        return gate.isRuntimeDcpDisabled() == true
    end
    if type(gate.isRuntimeDcpPersistenceDisabled) == "function" then
        return gate.isRuntimeDcpPersistenceDisabled() == true
    end
    return false
end

function M.resolveRuntimeDcpGatePolicy(context, emulsionId)
    if not M.isRuntimeDcpGateActive(context, emulsionId) then
        return {
            active = false,
            applyCameraProfile = false,
            status = "allowed",
            reason = "runtime_dcp_enabled",
            runtimeDcpDisabled = false,
        }
    end
    local gate = getDcpRuntimeGate(context or {})
    local reason = "runtime_dcp_disabled_c6h"
    local status = "profile_runtime_dcp_disabled"
    if gate then
        if type(gate.gateReasonCode) == "function" then
            reason = gate.gateReasonCode()
        end
        if type(gate.gateStatus) == "function" then
            status = gate.gateStatus()
        end
    end
    return {
        active = true,
        applyCameraProfile = false,
        status = status,
        reason = reason,
        runtimeDcpDisabled = true,
    }
end

local profileInstallerModule = nil
local function getProfileInstaller(context)
    if profileInstallerModule then
        return profileInstallerModule
    end
    local root = pluginRoot(context)
    if not root then
        return nil
    end
    local ok, mod = pcall(dofile, LrPathUtils.child(root, "lib/ProfileInstaller.lua"))
    if ok and type(mod) == "table" then
        profileInstallerModule = mod
        return mod
    end
    return nil
end

local function resolveDcpAssetPaths(dcpFile, context)
    local installer = getProfileInstaller(context)
    if installer and type(installer.resolveDcpAssetPaths) == "function" then
        return installer.resolveDcpAssetPaths(pluginRoot(context), dcpFile)
    end
    local filename = trim(dcpFile)
    local root = pluginRoot(context)
    local bundlePath = (root and filename ~= "") and LrPathUtils.child(root, "profiles/dcp/" .. filename) or ""
    local bundleExists = false
    if bundlePath ~= "" then
        local ok, exists = pcall(function()
            return LrFileUtils.exists(bundlePath)
        end)
        bundleExists = ok and exists == true
    end
    return {
        dcp_filename = filename,
        dcp_plugin_bundle_path = bundlePath,
        dcp_installed_path = "",
        gate_dcp_configured = filename ~= "",
        gate_dcp_plugin_bundle_exists = bundleExists,
        gate_dcp_installed_exists = false,
        gate_dcp_any_valid_source_exists = bundleExists,
    }
end

function M.normalizeProfileName(value)
    local text = trim(value):lower()
    text = text:gsub("%.%.%.%.?$", "")
    text = text:gsub("%s+", " ")
    return text
end

function M.profilesMatch(expected, actual)
    local expectedRaw = trim(expected)
    local actualRaw = trim(actual)
    if expectedRaw == "" or actualRaw == "" then
        return false, "empty"
    end
    if actualRaw == expectedRaw then
        return true, "exact"
    end
    local expectedNorm = M.normalizeProfileName(expectedRaw)
    local actualNorm = M.normalizeProfileName(actualRaw)
    if expectedNorm == actualNorm then
        return true, "normalized"
    end
    if actualNorm:find(expectedNorm, 1, true) == 1 or expectedNorm:find(actualNorm, 1, true) == 1 then
        return true, "prefix"
    end
    if actualNorm:find("mindfullens", 1, true) and expectedNorm:find("mindfullens", 1, true) then
        return true, "mindfullens_family"
    end
    return false, "mismatch"
end

function M.classifyProfileVerification(expectedProfile, readback, readbackAfterRetry, applyAttempted, policyState)
    if applyAttempted ~= true then
        local checkboxEffective = policyState and policyState.lab_profile_checkbox_effective == true
        local policyResult = tostring((policyState and policyState.profile_policy_result) or "")
        local assetStatus = tostring((policyState and policyState.dcp_asset_debug and policyState.dcp_asset_debug.final_asset_status) or "")

        if checkboxEffective ~= true then
            return "profile_skipped_user_disabled", "not_attempted_checkbox_off", false
        end
        if policyResult == "profile_missing_dcp_file" or assetStatus == "profile_missing_dcp_file" then
            return "profile_missing_dcp_file", "dcp_asset_missing", false
        end
        if policyResult == "profile_install_required" or assetStatus == "profile_install_required" then
            return "profile_install_required", "dcp_install_required", false
        end
        if policyResult == "profile_skipped_non_lab" then
            return "profile_skipped_non_lab", "non_lab", false
        end
        if policyResult ~= "" then
            return policyResult, "policy_blocked", false
        end
        return "profile_apply_attempted_unverified", "not_attempted", false
    end

    local expected = trim(expectedProfile)
    if expected == "" then
        return "profile_name_unresolved", "missing_expected", false
    end

    local primaryReadback = trim(readbackAfterRetry)
    if primaryReadback == "" then
        primaryReadback = trim(readback)
    end

    if primaryReadback ~= "" then
        local matched, matchKind = M.profilesMatch(expected, primaryReadback)
        if matched then
            if matchKind == "exact" then
                return "profile_applied_verified", matchKind, true
            end
            return "profile_applied_verified", matchKind, true
        end
        if M.normalizeProfileName(primaryReadback):find("mindfullens", 1, true) then
            return "profile_applied_ui_possible_readback_mismatch", "mindfullens_readback", true
        end
        return "profile_name_mismatch", "name_mismatch", false
    end

    local recognitionInfo = policyState and policyState.dcp_recognition_debug or nil
    local dcpInternal = trim(recognitionInfo and recognitionInfo.dcp_internal_profile_name or "")
    local altReadback = trim(recognitionInfo and recognitionInfo.profile_key_readback or "")
    if altReadback ~= "" then
        local altMatched, altKind = M.profilesMatch(expected, altReadback)
        if altMatched then
            return "profile_applied_ui_possible_readback_mismatch", "profile_key_readback:" .. tostring(altKind), true
        end
    end

    local assetReady = policyState
        and policyState.dcp_asset_debug
        and policyState.dcp_asset_debug.final_asset_status == "profile_ready_for_apply"
    local configuredMatchesInternal = recognitionInfo and recognitionInfo.profile_name_match == true
    local profileState = tostring((policyState and policyState.profile_state) or "")
    local dcpResolvedInEngine = profileState == "dcp_installed_existing"
        or profileState == "dcp_installed_root"
        or profileState == "dcp_forced_root"

    if dcpInternal ~= "" and not M.profilesMatch(expected, dcpInternal) then
        return "profile_internal_name_mismatch", "config_vs_dcp_internal", false
    end

    if assetReady or configuredMatchesInternal or dcpResolvedInEngine then
        return "profile_name_unresolved_by_lightroom", "readback_empty_lr_unresolved", true
    end

    if recognitionInfo and tostring(recognitionInfo.camera_profile_digest_readback or "") == "" and dcpInternal ~= "" then
        return "profile_digest_required_possible", "digest_absent_readback_empty", true
    end

    if applyAttempted == true and expected ~= "" then
        return "profile_name_unresolved_by_lightroom", "readback_empty_after_apply", true
    end

    return "profile_apply_attempted_unverified", "readback_unavailable", true
end

local function resolveEmulsionForPolicy(canonicalId, emulsion, context)
    if emulsion and emulsion.dcpFile and trim(emulsion.dcpFile) ~= "" then
        return emulsion
    end
    local config = context and context.config
    if config and type(config.findEmulsion) == "function" then
        local id = trim(canonicalId)
        if id ~= "" then
            return config.findEmulsion(id) or emulsion
        end
    end
    return emulsion
end

function M.buildProfileGateTrace(emulsionId, checkboxEnabled, emulsion, context)
    context = context or {}
    local id = trim(emulsionId)
    emulsion = resolveEmulsionForPolicy(id, emulsion, context)

    local profileApplyEligible = M.isCameraProfileApplyEligible(id)
    local labAllowlist = M.isLabCameraProfileEligible(id)
    local creativeAllowlist = M.isCreativeCameraProfileEligible(id)
    local checkboxEffective = checkboxEnabled == true
    local dcpFile = emulsion and trim(emulsion.dcpFile) or ""
    local profileName = emulsion and trim(emulsion.profile) or ""
    local dcpPaths = resolveDcpAssetPaths(dcpFile, context)
    local dcpConfigured = dcpPaths.gate_dcp_configured == true
    local profileNameConfigured = profileName ~= ""

    local globalDefaultBlocks = false
    if type(context.config) == "table" and context.config.defaultApplyCameraProfile ~= true then
        globalDefaultBlocks = true
    end

    local trace = {
        gate_lab_allowlist = labAllowlist,
        gate_creative_allowlist = creativeAllowlist,
        gate_profile_apply_eligible = profileApplyEligible,
        gate_checkbox_effective = checkboxEffective,
        gate_user_disabled = profileApplyEligible and not checkboxEffective,
        gate_runtime_dcp_disabled = false,
        runtime_dcp_gate_reason = "runtime_dcp_enabled",
        runtime_dcp_gate_status = "allowed",
        gate_dcp_configured = dcpConfigured,
        gate_dcp_file_configured = dcpConfigured,
        gate_dcp_plugin_bundle_exists = dcpPaths.gate_dcp_plugin_bundle_exists == true,
        gate_dcp_installed_exists = dcpPaths.gate_dcp_installed_exists == true,
        gate_dcp_any_valid_source_exists = dcpPaths.gate_dcp_any_valid_source_exists == true,
        gate_dcp_plugin_bundle_probe_known = dcpPaths.gate_dcp_plugin_bundle_probe_known == true,
        gate_dcp_installed_probe_known = dcpPaths.gate_dcp_installed_probe_known == true,
        gate_dcp_file_exists = triStateBool(dcpPaths.gate_dcp_plugin_bundle_exists),
        gate_profile_installed = triStateBool(dcpPaths.gate_dcp_installed_exists),
        gate_profile_name_resolved = profileNameConfigured,
        gate_global_default_blocks = globalDefaultBlocks,
        gate_non_lab_blocks = not profileApplyEligible,
        dcp_plugin_bundle_path = dcpPaths.dcp_plugin_bundle_path or "",
        dcp_installed_path = dcpPaths.dcp_installed_path or "",
        dcp_filename = dcpPaths.dcp_filename or dcpFile,
        profile_name_configured = profileName,
        install_required = dcpPaths.install_required == true,
        restart_required = dcpPaths.restart_required == true,
        final_asset_status = dcpPaths.final_asset_status or "unknown",
        final_applyCameraProfile_resolved = false,
        final_block_reason = "unknown",
    }

    if not profileApplyEligible then
        trace.final_block_reason = "profile_skipped_non_lab"
        return trace
    end

    local runtimeGatePolicy = M.resolveRuntimeDcpGatePolicy(context, id)
    if runtimeGatePolicy.active == true then
        trace.gate_runtime_dcp_disabled = true
        trace.runtime_dcp_gate_reason = runtimeGatePolicy.reason
        trace.runtime_dcp_gate_status = runtimeGatePolicy.status
        trace.final_block_reason = runtimeGatePolicy.status
        trace.final_applyCameraProfile_resolved = false
        return trace
    end
    trace.gate_runtime_dcp_disabled = false
    trace.runtime_dcp_gate_reason = runtimeGatePolicy.reason
    trace.runtime_dcp_gate_status = runtimeGatePolicy.status

    if not checkboxEffective then
        trace.final_block_reason = "profile_skipped_user_disabled"
        return trace
    end
    if not dcpConfigured then
        trace.final_block_reason = "profile_missing_dcp_file"
        return trace
    end
    if not profileNameConfigured then
        trace.final_block_reason = "profile_name_unresolved"
        return trace
    end

    if dcpPaths.final_asset_status == "profile_missing_dcp_file" then
        trace.final_block_reason = "profile_missing_dcp_file"
        return trace
    end

    trace.final_applyCameraProfile_resolved = true
    if dcpPaths.final_asset_status == "profile_install_required" then
        trace.final_block_reason = "profile_install_required"
    elseif dcpPaths.final_asset_status == "profile_ready_for_apply" then
        trace.final_block_reason = "profile_eligible_apply_attempt"
    else
        trace.final_block_reason = "profile_installation_unknown"
    end
    return trace
end

function M.resolveApplyCameraProfile(emulsionId, checkboxEnabled, emulsion, context)
    local trace = M.buildProfileGateTrace(emulsionId, checkboxEnabled, emulsion, context)
    if trace.final_applyCameraProfile_resolved == true then
        return true, trace.final_block_reason, trace
    end
    return false, trace.final_block_reason, trace
end

function M.resolveLabProfileCheckboxRaw(props)
    if type(props) ~= "table" then
        return nil, "missing_props"
    end
    if props.useAnalogLabCameraProfile ~= nil then
        return props.useAnalogLabCameraProfile == true, "useAnalogLabCameraProfile"
    end
    if props.applyCameraProfile ~= nil then
        return props.applyCameraProfile == true, "applyCameraProfile_legacy"
    end
    return nil, "unset"
end

function M.resolveLabProfileCheckboxState(props, canonicalId, emulsion, context)
    context = context or {}
    local id = trim(canonicalId)
    emulsion = resolveEmulsionForPolicy(id, emulsion, context)
    local profileApplyEligible = M.isCameraProfileApplyEligible(id)
    local labEligible = M.isLabCameraProfileEligible(id)
    local creativeEligible = M.isCreativeCameraProfileEligible(id)
    local defaultForEmulsion = profileApplyEligible and (M.defaultCheckboxForEmulsion(id) == true) or false
    local rawValue, rawSource = M.resolveLabProfileCheckboxRaw(props)
    local effective = false
    local policyInputSource = "non_lab_forced_off"

    if profileApplyEligible then
        if rawValue == nil then
            effective = defaultForEmulsion == true
            if creativeEligible and not labEligible then
                policyInputSource = "creative_default_on_unset"
            else
                policyInputSource = "lab_default_on_unset"
            end
        else
            effective = rawValue == true
            policyInputSource = rawSource or "useAnalogLabCameraProfile"
        end
    end

    local applyFlag, profilePolicyResult, gateTrace = M.resolveApplyCameraProfile(id, effective, emulsion, context)
    local dcpAssetDebug = M.buildDcpAssetDebugInfo(emulsion, context)
    local dcpRecognitionDebug = M.buildDcpRecognitionDebugInfo(emulsion, context, nil, nil)

    return {
        lab_allowlist_match = labEligible,
        creative_allowlist_match = creativeEligible,
        profile_apply_eligible = profileApplyEligible,
        lab_profile_checkbox_value_raw = rawValue,
        lab_profile_checkbox_effective = effective == true,
        lab_profile_default_for_emulsion = defaultForEmulsion,
        lab_profile_checkbox_enabled = profileApplyEligible,
        lab_profile_policy_input_source = policyInputSource,
        applyCameraProfile_resolved = applyFlag == true,
        profile_policy_result = profilePolicyResult,
        profile_gate_trace = gateTrace,
        runtime_dcp_disabled = gateTrace and gateTrace.gate_runtime_dcp_disabled == true,
        runtime_dcp_gate_reason = gateTrace and gateTrace.runtime_dcp_gate_reason or "",
        runtime_dcp_gate_status = gateTrace and gateTrace.runtime_dcp_gate_status or "",
        dcp_asset_debug = dcpAssetDebug,
        dcp_recognition_debug = dcpRecognitionDebug,
    }
end

function M.resolveCanonicalEmulsionId(input, context)
    context = context or {}
    local config = context.config
    local source = "empty"

    if type(input) == "table" and input.id then
        return trim(input.id), "emulsion.id"
    end

    local raw = trim(input)
    if raw == "" and type(context.props) == "table" then
        local props = context.props
        if type(props.emulsion) == "table" and props.emulsion.id then
            return trim(props.emulsion.id), "props.emulsion.id"
        end
        if props.selectedEmulsionId and trim(props.selectedEmulsionId) ~= "" then
            raw = trim(props.selectedEmulsionId)
            source = "props.selectedEmulsionId"
        elseif props.emulsionId and trim(props.emulsionId) ~= "" then
            raw = trim(props.emulsionId)
            source = "props.emulsionId"
        elseif props.filmId and trim(props.filmId) ~= "" then
            raw = trim(props.filmId)
            source = "props.filmId"
        elseif props.emulsion and trim(props.emulsion) ~= "" then
            raw = trim(props.emulsion)
            source = "props.emulsion"
        end
    end

    if raw == "" and context.emulsionId and trim(context.emulsionId) ~= "" then
        raw = trim(context.emulsionId)
        source = "context.emulsionId"
    end

    if raw == "" and type(context.runSummary) == "table" then
        if context.runSummary.resolvedEmulsionId and trim(context.runSummary.resolvedEmulsionId) ~= "" then
            raw = trim(context.runSummary.resolvedEmulsionId)
            source = "runSummary.resolvedEmulsionId"
        elseif context.runSummary.emulsionId and trim(context.runSummary.emulsionId) ~= "" then
            raw = trim(context.runSummary.emulsionId)
            source = "runSummary.emulsionId"
        end
    end

    if raw ~= "" and config and type(config.findEmulsion) == "function" then
        local resolvedEmulsion = config.findEmulsion(raw)
        if resolvedEmulsion and resolvedEmulsion.id then
            return trim(resolvedEmulsion.id), source ~= "empty" and source or "config.findEmulsion"
        end
    end

    if raw ~= "" then
        return raw, source ~= "empty" and source or "raw_fallback"
    end

    return "", "empty"
end

function M.buildPolicyTrace(input, context)
    context = context or {}
    local config = context.config
    local rawSelected = trim(input)
    if rawSelected == "" and type(context.props) == "table" then
        rawSelected = trim(context.props.emulsion or context.props.emulsionId or context.props.filmId or "")
    end
    local canonicalId, policyInputSource = M.resolveCanonicalEmulsionId(input, context)
    local emulsion = (config and canonicalId ~= "" and config.findEmulsion) and config.findEmulsion(canonicalId) or nil
    local workflowCode = ""
    if type(context.props) == "table" then
        workflowCode = trim(context.props.smartMatchWorkflow or "")
    end
    local checkboxState = M.resolveLabProfileCheckboxState(context.props, canonicalId, emulsion, context)
    local gateTrace = checkboxState.profile_gate_trace or {}
    return {
        raw_selected_value = rawSelected ~= "" and rawSelected or "unknown",
        workflow_code = workflowCode ~= "" and workflowCode or "unknown",
        canonical_emulsion_id = canonicalId ~= "" and canonicalId or "unknown",
        policy_input_source = policyInputSource,
        visible_label = emulsion and (emulsion.clientFacingLabel or emulsion.publicLabel or emulsion.label) or "unknown",
        lab_allowlist_match = checkboxState.lab_allowlist_match,
        lab_profile_checkbox_value_raw = checkboxState.lab_profile_checkbox_value_raw,
        lab_profile_checkbox_effective = checkboxState.lab_profile_checkbox_effective,
        lab_profile_default_for_emulsion = checkboxState.lab_profile_default_for_emulsion,
        lab_profile_checkbox_enabled = checkboxState.lab_profile_checkbox_enabled,
        lab_profile_policy_input_source = checkboxState.lab_profile_policy_input_source,
        applyCameraProfile_resolved = checkboxState.applyCameraProfile_resolved,
        profile_policy_result = checkboxState.profile_policy_result,
        apply_camera_profile_resolved = checkboxState.applyCameraProfile_resolved,
        profile_gate_trace = gateTrace,
    }
end

function M.previewApplyAligned(emulsionId, checkboxEnabled, emulsion, context)
    local applyFlag, applyStatus = M.resolveApplyCameraProfile(emulsionId, checkboxEnabled, emulsion, context)
    local previewFlag, previewStatus = M.resolveApplyCameraProfile(emulsionId, checkboxEnabled, emulsion, context)
    if applyFlag == previewFlag and applyStatus == previewStatus then
        return true, "profile_preview_apply_aligned"
    end
    return false, "profile_preview_apply_mismatch"
end

local DISPLAY_LABELS = {
    profile_skipped_by_policy = "skipped (policy)",
    profile_skipped_user_disabled = "skipped (user disabled)",
    profile_skipped_global_default = "skipped (global default)",
    profile_skipped_non_lab = "skipped (non-Lab film)",
    profile_missing = "missing",
    profile_missing_dcp_file = "missing DCP file",
    profile_name_unresolved = "profile name unresolved",
    profile_name_mismatch = "name mismatch",
    profile_installation_unknown = "installation unknown",
    profile_install_required = "install required",
    profile_ready_for_apply = "ready for apply",
    profile_eligible_apply_attempt = "eligible (apply attempt)",
    profile_runtime_dcp_disabled = "runtime DCP disabled",
    profile_engine_not_requested = "engine did not request profile",
    profile_applied_unverified = "applied (unverified)",
    profile_applied_verified = "applied (verified)",
    profile_applied_ui_possible_readback_mismatch = "applied (UI/readback mismatch)",
    profile_apply_attempted_unverified = "apply attempted (unverified)",
    profile_name_unresolved_by_lightroom = "unresolved by Lightroom",
    profile_internal_name_mismatch = "internal name mismatch",
    profile_requires_lightroom_restart = "requires Lightroom restart",
    profile_digest_required_possible = "digest required (possible)",
    profile_digest_required_confirmed = "digest required (confirmed)",
    profile_camera_incompatible_possible = "camera incompatible (possible)",
    profile_manual_selection_required = "manual selection required",
    profile_manual_selection_also_missing = "manual selection also missing",
    profile_registered_manual_ok_plugin_payload_incomplete = "manual OK / plugin payload incomplete",
    profile_registration_unresolved = "registration unresolved",
    profile_rejected = "rejected",
    profile_rejected_confirmed = "rejected (confirmed)",
    profile_apply_failed = "apply failed",
    profile_preview_apply_aligned = "camera profile preview/apply aligned",
    profile_preview_apply_mismatch = "preview/apply mismatch",
    profile_requested = "requested",
    skipped_disabled = "skipped (disabled)",
    missing = "missing",
    applied = "applied",
    rejected = "rejected",
    skipped = "skipped",
    unknown = "unknown",
}

function M.displayLabel(statusCode)
    local key = tostring(statusCode or "unknown")
    return DISPLAY_LABELS[key] or key
end

function M.resolveDisplayStatus(emulsionId, checkboxEnabled, runSummary, emulsion, policyState)
    local id = trim(emulsionId)
    if M.isCameraProfileApplyEligible(id) then
        local gateTrace = policyState and policyState.profile_gate_trace
        if gateTrace and gateTrace.gate_runtime_dcp_disabled == true then
            return "profile_runtime_dcp_disabled"
        end
        if M.isRuntimeDcpGateActive({
            pluginPath = policyState and policyState.pluginPath or nil,
            config = policyState and policyState.config or nil,
        }, id) then
            return "profile_runtime_dcp_disabled"
        end
    end

    local resolverEligible = policyState and policyState.applyCameraProfile_resolved == true
    local resolverResult = policyState and policyState.profile_policy_result or ""

    if not M.isCameraProfileApplyEligible(id) then
        return "profile_skipped_non_lab"
    end
    if checkboxEnabled ~= true then
        return "profile_skipped_user_disabled"
    end
    if resolverEligible ~= true then
        if resolverResult ~= "" then
            return resolverResult
        end
        return "profile_skipped_by_policy"
    end

    if not policyState.dcp_recognition_debug and type(M.buildDcpRecognitionDebugInfo) == "function" then
        policyState.dcp_recognition_debug = M.buildDcpRecognitionDebugInfo(
            emulsion,
            { pluginPath = policyState.pluginPath, config = policyState.config },
            runSummary,
            nil
        )
    end

    local profileName = tostring((runSummary and runSummary.cameraProfileName) or (runSummary and runSummary.cameraProfile) or (emulsion and emulsion.profile) or "")
    local readback = tostring((runSummary and runSummary.cameraProfileReadback) or "")
    local readbackRetry = tostring((runSummary and runSummary.cameraProfileReadbackAfterRetry) or "")
    local applyAttempted = resolverEligible == true and (runSummary and runSummary.cameraProfileApplyAttempted == true)

    local classified, matchKind = M.classifyProfileVerification(
        profileName,
        readback,
        readbackRetry,
        applyAttempted,
        policyState
    )

    local engineStatus = tostring((runSummary and runSummary.cameraProfileStatus) or "")
    local engineState = tostring((runSummary and runSummary.cameraProfileState) or "")
    if engineStatus == "missing" or engineState == "dcp_missing_root" or engineState == "missing_config" then
        if classified == "profile_apply_attempted_unverified" or classified == "profile_applied_ui_possible_readback_mismatch" or classified == "profile_applied_verified" then
            return classified
        end
        return "profile_missing_dcp_file"
    end
    if engineStatus == "requested" or engineStatus == "applied" then
        return classified
    end
    if engineStatus == "rejected" or engineStatus == "profile_rejected" then
        if classified == "profile_applied_verified"
            or classified == "profile_applied_ui_possible_readback_mismatch"
            or classified == "profile_apply_attempted_unverified" then
            return classified
        end
        return "profile_rejected_confirmed"
    end
    if engineStatus == "skipped_disabled" or engineStatus == "skipped" then
        if resolverEligible == true then
            return "profile_engine_not_requested"
        end
        return "profile_skipped_user_disabled"
    end
    if classified ~= "profile_skipped_user_disabled" then
        return classified
    end
    if resolverResult == "profile_installation_unknown" then
        return "profile_installation_unknown"
    end
    if resolverEligible == true then
        return resolverResult ~= "" and resolverResult or "profile_eligible_apply_attempt"
    end
    if engineStatus == "" or engineStatus == "unknown" then
        return "profile_apply_failed"
    end
    return engineStatus
end

function M.formatBackupRenderStatus(rawStatus, profileDisplayStatus)
    local raw = tostring(rawStatus or "unknown")
    local profileStatus = tostring(profileDisplayStatus or "")
    if profileStatus == "profile_runtime_dcp_disabled" then
        return "skipped (runtime DCP disabled by C6H gate)"
    end
    if raw == "skipped_no_reject" then
        if profileStatus == "profile_skipped_by_policy"
            or profileStatus == "profile_skipped_user_disabled"
            or profileStatus == "profile_skipped_non_lab"
            or profileStatus == "profile_missing"
            or profileStatus == "profile_missing_dcp_file" then
            return "skipped (profile skipped by policy)"
        end
        return "skipped (profile not rejected)"
    end
    return raw
end

function M.formatProfileGateTraceLines(gateTrace)
    gateTrace = gateTrace or {}
    return {
        "gate_lab_allowlist = " .. triStateBool(gateTrace.gate_lab_allowlist),
        "gate_creative_allowlist = " .. triStateBool(gateTrace.gate_creative_allowlist),
        "gate_profile_apply_eligible = " .. triStateBool(gateTrace.gate_profile_apply_eligible),
        "gate_checkbox_effective = " .. triStateBool(gateTrace.gate_checkbox_effective),
        "gate_user_disabled = " .. triStateBool(gateTrace.gate_user_disabled),
        "gate_dcp_configured = " .. triStateBool(gateTrace.gate_dcp_configured),
        "gate_dcp_plugin_bundle_exists = " .. triStateBool(gateTrace.gate_dcp_plugin_bundle_exists),
        "gate_dcp_installed_exists = " .. tostring(gateTrace.gate_dcp_installed_exists or "unknown"),
        "gate_dcp_any_valid_source_exists = " .. tostring(gateTrace.gate_dcp_any_valid_source_exists or "unknown"),
        "gate_dcp_plugin_bundle_probe_known = " .. triStateBool(gateTrace.gate_dcp_plugin_bundle_probe_known),
        "gate_dcp_installed_probe_known = " .. triStateBool(gateTrace.gate_dcp_installed_probe_known),
        "gate_profile_name_resolved = " .. triStateBool(gateTrace.gate_profile_name_resolved),
        "gate_global_default_blocks = " .. triStateBool(gateTrace.gate_global_default_blocks),
        "gate_non_lab_blocks = " .. triStateBool(gateTrace.gate_non_lab_blocks),
        "gate_runtime_dcp_disabled = " .. triStateBool(gateTrace.gate_runtime_dcp_disabled),
        "runtime_dcp_gate_reason = " .. tostring(gateTrace.runtime_dcp_gate_reason or "unknown"),
        "runtime_dcp_gate_status = " .. tostring(gateTrace.runtime_dcp_gate_status or "unknown"),
        "dcp_filename = " .. tostring(gateTrace.dcp_filename or ""),
        "dcp_plugin_bundle_path = " .. tostring(gateTrace.dcp_plugin_bundle_path or ""),
        "dcp_installed_path = " .. tostring(gateTrace.dcp_installed_path or ""),
        "profile_name_configured = " .. tostring(gateTrace.profile_name_configured or ""),
        "install_required = " .. triStateBool(gateTrace.install_required),
        "restart_required = " .. triStateBool(gateTrace.restart_required),
        "final_asset_status = " .. tostring(gateTrace.final_asset_status or "unknown"),
        "final_applyCameraProfile_resolved = " .. triStateBool(gateTrace.final_applyCameraProfile_resolved),
        "final_block_reason = " .. tostring(gateTrace.final_block_reason or "unknown"),
    }
end

local function applyAttemptedFromSummary(runSummary)
    return runSummary and runSummary.cameraProfileApplyAttempted == true
end

function M.buildDcpRecognitionDebugInfo(emulsion, context, runSummary, uiProfileMissingObserved)
    context = context or {}
    emulsion = emulsion or {}
    local configured = trim(emulsion.profile)
    local dcpFile = trim(emulsion.dcpFile)
    local installer = getProfileInstaller(context)
    local internalName = ""
    local internalMethod = "none"
    local profileNameMatch = false

    if installer and type(installer.resolveDcpProfileRecognition) == "function" then
        local recognition = installer.resolveDcpProfileRecognition(pluginRoot(context), dcpFile, configured)
        internalName = trim(recognition.dcp_internal_profile_name)
        internalMethod = tostring(recognition.internal_name_probe_method or "none")
        profileNameMatch = recognition.profile_name_match == true
    end

    local written = tostring((runSummary and runSummary.cameraProfileWritten) or (runSummary and runSummary.cameraProfileName) or configured)
    local readback = tostring((runSummary and runSummary.cameraProfileReadbackAfterRetry) or (runSummary and runSummary.cameraProfileReadback) or "")
    local profileKeyReadback = tostring((runSummary and runSummary.profileKeyReadback) or "")
    local digestReadback = tostring((runSummary and runSummary.cameraProfileDigestReadback) or "")
    local uiObserved = "none"
    if uiProfileMissingObserved == true then
        uiObserved = "manual"
    elseif applyAttemptedFromSummary(runSummary) and readback == "" and profileNameMatch then
        uiObserved = "inferred_from_readback_empty"
    end

    local likelyCause = "unknown"
    if not profileNameMatch and internalName ~= "" then
        likelyCause = "configured_profile_name_differs_from_dcp_internal_name"
    elseif profileNameMatch and readback == "" then
        likelyCause = "lightroom_profile_registered_but_api_readback_empty_or_ui_profile_missing"
        if digestReadback == "" then
            likelyCause = likelyCause .. "|camera_profile_digest_absent_possible_c6c"
        end
    elseif readback ~= "" then
        likelyCause = "readback_present"
    end

    return {
        configured_profile_name = configured,
        dcp_internal_profile_name = internalName,
        profile_name_match = profileNameMatch,
        internal_name_probe_method = internalMethod,
        camera_profile_written = written,
        camera_profile_readback = readback,
        profile_key_readback = profileKeyReadback,
        camera_profile_digest_readback = digestReadback,
        profile_missing_ui_observed = uiObserved,
        likely_cause = likelyCause,
    }
end

function M.formatDcpRecognitionDebugLines(debugInfo)
    debugInfo = debugInfo or {}
    return {
        "configured profile name = " .. tostring(debugInfo.configured_profile_name or ""),
        "DCP internal profile name = " .. tostring(debugInfo.dcp_internal_profile_name or ""),
        "profile name match = " .. triStateBool(debugInfo.profile_name_match),
        "internal name probe method = " .. tostring(debugInfo.internal_name_probe_method or ""),
        "CameraProfile written = " .. tostring(debugInfo.camera_profile_written or ""),
        "CameraProfile readback = " .. tostring(debugInfo.camera_profile_readback or ""),
        "Profile key readback = " .. tostring(debugInfo.profile_key_readback or ""),
        "CameraProfileDigest readback = " .. tostring(debugInfo.camera_profile_digest_readback or ""),
        "profile_missing_ui_observed = " .. tostring(debugInfo.profile_missing_ui_observed or "none"),
        "likely cause = " .. tostring(debugInfo.likely_cause or "unknown"),
    }
end

function M.buildDcpAssetDebugInfo(emulsion, context)
    context = context or {}
    local dcpFile = emulsion and trim(emulsion.dcpFile) or ""
    local dcpPaths = resolveDcpAssetPaths(dcpFile, context)
    local nearest = dcpPaths.nearest_dcp_matches or {}
    local nearestText = table.concat(nearest, ", ")
    return {
        dcp_filename = dcpPaths.dcp_filename or dcpFile,
        config_dcpFile = dcpPaths.config_dcpFile or dcpFile,
        active_plugin_dcp_exists = dcpPaths.gate_dcp_plugin_bundle_exists,
        active_plugin_dcp_path = dcpPaths.active_plugin_dcp_path or dcpPaths.dcp_plugin_bundle_path or "",
        installed_dcp_exists = dcpPaths.gate_dcp_installed_exists,
        installed_dcp_path = dcpPaths.installed_dcp_path or dcpPaths.dcp_installed_path or "",
        golden_dcp_exists = "not_checked_in_runtime",
        nearest_dcp_matches = nearestText,
        install_required = dcpPaths.install_required == true,
        restart_required = dcpPaths.restart_required == true,
        final_asset_status = dcpPaths.final_asset_status or "unknown",
        bundle_probe_method = dcpPaths.gate_dcp_plugin_bundle_probe_method or "",
        installed_probe_method = dcpPaths.gate_dcp_installed_probe_method or "",
    }
end

function M.formatDcpAssetDebugLines(debugInfo)
    debugInfo = debugInfo or {}
    return {
        "dcp_filename = " .. tostring(debugInfo.dcp_filename or ""),
        "config_dcpFile = " .. tostring(debugInfo.config_dcpFile or ""),
        "active_plugin_dcp_exists = " .. triStateBool(debugInfo.active_plugin_dcp_exists),
        "active_plugin_dcp_path = " .. tostring(debugInfo.active_plugin_dcp_path or ""),
        "installed_dcp_exists = " .. triStateBool(debugInfo.installed_dcp_exists),
        "installed_dcp_path = " .. tostring(debugInfo.installed_dcp_path or ""),
        "golden_dcp_exists = " .. tostring(debugInfo.golden_dcp_exists or "not_checked_in_runtime"),
        "nearest_dcp_matches = " .. tostring(debugInfo.nearest_dcp_matches or ""),
        "install_required = " .. triStateBool(debugInfo.install_required),
        "restart_required = " .. triStateBool(debugInfo.restart_required),
        "final_asset_status = " .. tostring(debugInfo.final_asset_status or "unknown"),
        "bundle_probe_method = " .. tostring(debugInfo.bundle_probe_method or ""),
        "installed_probe_method = " .. tostring(debugInfo.installed_probe_method or ""),
    }
end

function M.formatCameraProfileDebugLines(debugInfo)
    debugInfo = debugInfo or {}
    return {
        "expected profile name = " .. tostring(debugInfo.expected_profile_name or ""),
        "written profile name = " .. tostring(debugInfo.written_profile_name or ""),
        "readback profile name = " .. tostring(debugInfo.readback_profile_name or ""),
        "plugin DCP path exists = " .. triStateBool(debugInfo.plugin_dcp_path_exists),
        "installed DCP path exists = " .. triStateBool(debugInfo.installed_dcp_path_exists),
        "profile apply attempted = " .. triStateBool(debugInfo.profile_apply_attempted),
        "profile verification status = " .. tostring(debugInfo.profile_verification_status or "unknown"),
        "profile match kind = " .. tostring(debugInfo.profile_match_kind or "unknown"),
        "final profile verdict = " .. tostring(debugInfo.final_profile_verdict or "unknown"),
    }
end

function M.buildCameraProfileDebugInfo(runSummary, emulsion, policyState)
    local expected = tostring((emulsion and emulsion.profile) or (runSummary and runSummary.cameraProfileName) or "")
    local written = tostring((runSummary and runSummary.cameraProfileName) or (runSummary and runSummary.cameraProfile) or expected)
    local readback = tostring((runSummary and runSummary.cameraProfileReadbackAfterRetry) or (runSummary and runSummary.cameraProfileReadback) or "")
    local gateTrace = policyState and policyState.profile_gate_trace or {}
    local applyAttempted = (policyState and policyState.applyCameraProfile_resolved == true)
        or (runSummary and runSummary.cameraProfileApplyAttempted == true)

    policyState = policyState or {}
    if not policyState.dcp_asset_debug and policyState.profile_gate_trace then
        policyState.dcp_asset_debug = {
            final_asset_status = policyState.profile_gate_trace.final_asset_status,
        }
    end
    if not policyState.dcp_recognition_debug and type(M.buildDcpRecognitionDebugInfo) == "function" then
        policyState.dcp_recognition_debug = M.buildDcpRecognitionDebugInfo(emulsion, {
            pluginPath = policyState.pluginPath,
            config = policyState.config,
        }, runSummary, nil)
    end

    local verificationStatus = runSummary and runSummary.cameraProfileVerificationStatus
    local matchKind = runSummary and runSummary.cameraProfileMatchKind
    verificationStatus, matchKind = M.classifyProfileVerification(
        expected,
        runSummary and runSummary.cameraProfileReadback,
        runSummary and runSummary.cameraProfileReadbackAfterRetry,
        applyAttempted,
        policyState
    )
    local finalVerdict = verificationStatus
    if applyAttempted ~= true and policyState and policyState.profile_policy_result and policyState.profile_policy_result ~= "" then
        finalVerdict = tostring(policyState.profile_policy_result)
    end
    return {
        expected_profile_name = expected,
        written_profile_name = written,
        readback_profile_name = readback,
        plugin_dcp_path_exists = gateTrace.gate_dcp_plugin_bundle_exists,
        installed_dcp_path_exists = gateTrace.gate_dcp_installed_exists,
        profile_apply_attempted = applyAttempted,
        profile_verification_status = verificationStatus,
        profile_match_kind = matchKind,
        final_profile_verdict = finalVerdict,
        dcp_recognition_debug = policyState.dcp_recognition_debug,
    }
end

return M
