local MOD_NAME = "SurrounDeadOnline-Phase5H21Appearance"
local dataPath = [[__DATA_PATH__]]

local loadedPath =
    dataPath .. "\\lua_phase5h21_appearance_loaded.txt"

local triggerPath =
    dataPath .. "\\phase5h21.appearance_once.txt"

local disabledPath =
    dataPath .. "\\phase5h21.disable_appearance.txt"

local resultPath =
    dataPath .. "\\lua_phase5h21_appearance_result.txt"

local errorPath =
    dataPath .. "\\lua_phase5h21_appearance_error.txt"

local proxySpawnPath =
    dataPath .. "\\cpp_phase5g_spawn.txt"

local UEHelpers = require("UEHelpers")

local attempted = false
local stableTicks = 0

local function log(message)
    print(string.format("[%s] %s\n", MOD_NAME, tostring(message)))
end

local function fileExists(path)
    local file = io.open(path, "r")

    if file then
        file:close()
        return true
    end

    return false
end

local function atomicWrite(path, contents)
    local temporaryPath = path .. ".tmp"
    local file, errorMessage = io.open(temporaryPath, "w")

    if not file then
        return false, errorMessage
    end

    file:write(contents)
    file:flush()
    file:close()

    os.remove(path)

    local ok, renameError = os.rename(temporaryPath, path)

    if not ok then
        return false, renameError
    end

    return true, nil
end

local function safeName(object)
    if not object or not object:IsValid() then
        return "<nil>"
    end

    local ok, value = pcall(function()
        return object:GetFullName()
    end)

    if not ok then
        return "<name-error>"
    end

    return tostring(value):gsub("[\r\n\t]", " ")
end

local function getLocalPawn()
    local controller = UEHelpers:GetPlayerController()

    if not controller or not controller:IsValid() then
        return nil
    end

    local pawn = nil

    local ok, value = pcall(function()
        if controller.Pawn then
            return controller.Pawn
        end

        return controller:GetPawn()
    end)

    if ok then
        pawn = value
    end

    if not pawn or not pawn:IsValid() then
        return nil
    end

    return pawn
end

local function findProxy(localPawn)
    local actors = FindAllOf("BP_PlayerCharacter_C")

    if not actors then
        return nil
    end

    local localAddress = localPawn:GetAddress()
    local localWorld = localPawn:GetWorld()

    for _, actor in pairs(actors) do
        if actor and actor:IsValid() then
            local addressOk, address = pcall(function()
                return actor:GetAddress()
            end)

            local worldOk, world = pcall(function()
                return actor:GetWorld()
            end)

            if (
                addressOk and
                address ~= localAddress and
                worldOk and
                world and
                world:IsValid() and
                localWorld and
                localWorld:IsValid() and
                world:GetAddress() == localWorld:GetAddress()
            ) then
                return actor
            end
        end
    end

    return nil
end

local function applyAppearance()
    if attempted then
        return
    end

    if fileExists(disabledPath) then
        return
    end

    if not fileExists(triggerPath) then
        return
    end

    if not fileExists(proxySpawnPath) then
        stableTicks = 0
        return
    end

    local localPawn = getLocalPawn()

    if not localPawn then
        stableTicks = 0
        return
    end

    local proxy = findProxy(localPawn)

    if not proxy then
        stableTicks = 0
        return
    end

    stableTicks = stableTicks + 1

    -- Five seconds at a 200 ms interval. This keeps the proven movement
    -- pipeline fully live before adding clothing/equipment visuals once.
    if stableTicks < 25 then
        return
    end

    attempted = true

    os.remove(triggerPath)
    os.remove(resultPath)
    os.remove(errorPath)

    ExecuteInGameThread(function()
        local ok, errorMessage = pcall(function()
            proxy:LoadPlayerInventory()

            -- Preserve all confirmed stable proxy safety controls.
            proxy:SetActorHiddenInGame(false)
            proxy:SetActorEnableCollision(false)
            proxy:SetActorTickEnabled(false)
        end)

        if not ok then
            atomicWrite(
                errorPath,
                "phase=5H.2.1\n" ..
                "appearanceAttempted=1\n" ..
                "loadPlayerInventoryReturned=0\n" ..
                "localPawn=" .. safeName(localPawn) .. "\n" ..
                "proxyPawn=" .. safeName(proxy) .. "\n" ..
                "message=" .. tostring(errorMessage) .. "\n"
            )

            log(
                "Appearance initialization failed: " ..
                tostring(errorMessage)
            )

            return
        end

        atomicWrite(
            resultPath,
            "phase=5H.2.1\n" ..
            "appearanceAttempted=1\n" ..
            "loadPlayerInventoryReturned=1\n" ..
            "mode=appearance-only-on-stable-phase5g-proxy\n" ..
            "movementPipeline=local_state.tsv-to-remote_players.tsv\n" ..
            "trueIndependentLoadoutReplication=0\n" ..
            "localPawn=" .. safeName(localPawn) .. "\n" ..
            "proxyPawn=" .. safeName(proxy) .. "\n" ..
            "proxyTickDisabledRestored=1\n" ..
            "proxyCollisionDisabledRestored=1\n"
        )

        log("Appearance applied without replacing the live movement pipeline.")
    end)
end

os.remove(resultPath)
os.remove(errorPath)

atomicWrite(
    loadedPath,
    "phase=5H.2.1\n" ..
    "appearanceOnlyLoader=1\n" ..
    "appearanceFunction=LoadPlayerInventory\n" ..
    "writesMovementState=0\n" ..
    "writesLocalStateV2=0\n" ..
    "writesLocalStateV3=0\n" ..
    "globalObjectArrayScanUsed=0\n" ..
    "classReflectionScanUsed=0\n"
)

log("Appearance-only loader started.")
log("The confirmed Phase 5G movement bridge remains authoritative.")

LoopAsync(200, function()
    local ok, errorMessage = pcall(applyAppearance)

    if not ok and not attempted then
        atomicWrite(
            errorPath,
            "phase=5H.2.1\n" ..
            "appearanceAttempted=0\n" ..
            "message=" .. tostring(errorMessage) .. "\n"
        )
    end

    return false
end)
