-- Exposed Inspector properties. Keep these names unique in the scene.
playerName = "Player"
doorName = "Door"
statusTextName = "Status Text"
savePrefix = "two_room_relay"

local playerId = -1
local doorId = -1
local statusId = -1
local startPosition = nil
local doorOpen = false
local fields = { "x", "y", "z", "door_open" }

local function key(name)
    return savePrefix .. "." .. name
end

local function status(message)
    Log.Print(message)
    if statusId >= 0 then
        local display = string.sub(message, 1, 62)
        if #display > 32 then
            display = string.sub(display, 1, 32) .. "\n" .. string.sub(display, 33)
        end
        Text.SetText(statusId, display)
    end
end

local function valid_targets()
    if not GameObject.Exists(playerId) or not GameObject.Exists(doorId) then
        status("SETUP ERROR: CHECK PLAYER AND DOOR NAMES")
        return false
    end
    return true
end

local function set_door(open)
    if not GameObject.SetActive(doorId, not open) then
        status("SETUP ERROR: DOOR IS UNAVAILABLE")
        return false
    end
    doorOpen = open
    return true
end

local function capture_preferences()
    local previous = {}
    for _, name in ipairs(fields) do
        if PlayerPrefs.HasKey(key(name)) then
            previous[name] = name == "door_open"
                and PlayerPrefs.GetInt(key(name), 0)
                or PlayerPrefs.GetFloat(key(name), 0.0)
        end
    end
    return previous
end

local function clear_preferences()
    for _, name in ipairs(fields) do
        -- An absent key returns false; deleting it is already complete.
        PlayerPrefs.DeleteKey(key(name))
    end
end

local function restore_preferences(previous)
    -- Release newly inserted keys first, including a partially written set.
    clear_preferences()
    for _, name in ipairs(fields) do
        local value = previous[name]
        if value ~= nil then
            if name == "door_open" then
                PlayerPrefs.SetInt(key(name), value)
            else
                PlayerPrefs.SetFloat(key(name), value)
            end
        end
    end
end

local function save_checkpoint(hostId, message)
    if not valid_targets() then return false end
    local position = Transform.GetPosition(playerId)
    if position == nil then
        status("SAVE FAILED: PLAYER IS UNAVAILABLE")
        return false
    end
    local previous = capture_preferences()
    local written = PlayerPrefs.SetFloat(key("x"), position.x)
        and PlayerPrefs.SetFloat(key("y"), position.y)
        and PlayerPrefs.SetFloat(key("z"), position.z)
        and PlayerPrefs.SetInt(key("door_open"), doorOpen and 1 or 0)
    if not written or not PlayerPrefs.Save() then
        local reason = SaveData.GetLastError()
        restore_preferences(previous)
        status("SAVE FAILED - RETRY SAVE: " .. reason)
        return false
    end
    status(message)
    AudioSource.Play(hostId)
    return true
end

local function teleport(position)
    if not Transform.SetPosition(playerId, position.x, position.y, position.z) then
        status("LOAD FAILED: INVALID PLAYER POSITION")
        return false
    end
    CharacterController.SetVelocityY(playerId, 0.0)
    return true
end

local function load_checkpoint()
    if not valid_targets() then return false end
    local previous = capture_preferences()
    if not SaveData.Reload() then
        local reason = SaveData.GetLastError()
        -- Keep this checkpoint available when loading fails.
        restore_preferences(previous)
        status("LOAD FAILED: " .. reason)
        return false
    end
    for _, name in ipairs(fields) do
        if not PlayerPrefs.HasKey(key(name)) then
            status("NO CHECKPOINT")
            return false
        end
    end
    local position = {
        x = PlayerPrefs.GetFloat(key("x"), 1e30),
        y = PlayerPrefs.GetFloat(key("y"), 1e30),
        z = PlayerPrefs.GetFloat(key("z"), 1e30)
    }
    if not teleport(position) then return false end
    if not set_door(PlayerPrefs.GetInt(key("door_open"), 0) == 1) then return false end
    status("CHECKPOINT LOADED")
    return true
end

function Start(gameObjectId)
    statusId = UI.Find(statusTextName)
    playerId = GameObject.Find(playerName)
    doorId = GameObject.Find(doorName)
    if not valid_targets() then return end
    startPosition = Transform.GetPosition(playerId)
    set_door(PlayerPrefs.GetInt(key("door_open"), 0) == 1)
    local saveButton = UI.Find("Save Button")
    if saveButton >= 0 then Selectable.Select(saveButton) end
    status(doorOpen and "DOOR OPEN - SQUARE LOADS" or "WALK TO THE DOOR")
end

function Update(gameObjectId, dt)
    if Input.GetButtonDown(Input.SQUARE) then load_checkpoint() end
end

function OnTriggerEnter(gameObjectId, otherGameObjectId)
    if otherGameObjectId ~= playerId or doorOpen then return end
    if not valid_targets() or not set_door(true) then return end
    -- An unavailable card never blocks walking into Room B.
    -- Save retries persist this live door state and the current player position.
    save_checkpoint(gameObjectId, "DOOR OPEN - CHECKPOINT SAVED")
end

function saveNow(gameObjectId, elementId, elementName, eventName)
    save_checkpoint(gameObjectId, "CHECKPOINT SAVED")
end

function resetProgress(gameObjectId, elementId, elementName, eventName)
    if not valid_targets() or startPosition == nil then return end
    local previous = capture_preferences()
    clear_preferences()
    if not PlayerPrefs.Save() then
        local reason = SaveData.GetLastError()
        restore_preferences(previous)
        status("RESET FAILED: " .. reason)
        return
    end
    -- Return to Room A before closing the door, so reset cannot trap the player.
    if not teleport(startPosition) then return end
    if not set_door(false) then return end
    status("PROGRESS RESET")
end
