#include "..\script_component.hpp"

/*
 * File: fnc_initActorStore.sqf
 * Author: IDSolutions
 * Date: 2025-12-17
 * Last Update: 2026-04-05
 * Public: Yes
 *
 * Description:
 * Initializes the actor store for managing player actor data.
 * Actor hot state is owned by the extension; SQF acts as a thin bridge for
 * engine-adjacent reads, snapshots, and response fan-out.
 *
 * Arguments:
 * None
 *
 * Return Value:
 * Actor store object [HASHMAP OBJECT]
 *
 * Example:
 * call forge_server_actor_fnc_initActorStore
 */

#pragma hemtt ignore_variables ["_self"]
GVAR(ActorModel) = compileFinal createHashMapObject [[
    ["#type", "ActorModel"],
    ["defaults", compileFinal {
        private _actor = createHashMap;

        _actor set ["uid", ""];
        _actor set ["name", ""];
        _actor set ["loadout", [[],[],[],["U_BG_Guerrilla_6_1",[["FirstAidKit", 2]]],[],[],"H_Cap_blk_ION","",[],["ItemMap","ItemGPS","ItemRadio","ItemCompass","ItemWatch",""]]];
        _actor set ["position", [0,0,0]];
        _actor set ["direction", 0];
        _actor set ["stance", "STAND"];
        _actor set ["rank", "PRIVATE"];
        _actor set ["state", "HEALTHY"];
        _actor set ["phone_number", ""];
        _actor set ["email", ""];
        _actor set ["organization", "default"];
        _actor set ["holster", true];

        _actor
    }],
    ["fromPlayer", compileFinal {
        params [["_player", objNull, [objNull]]];

        if (_player isEqualTo objNull) exitWith { _self call ["defaults", []] };

        private _actor = _self call ["defaults", []];

        _actor set ["uid", getPlayerUID _player];
        _actor set ["name", name _player];
        _actor set ["position", getPosASL _player];
        _actor set ["direction", getDir _player];
        _actor set ["stance", stance _player];
        _actor set ["rank", rank _player];
        _actor set ["state", lifeState _player];

        _actor
    }],
    ["migrate", compileFinal {
        params [["_actor", createHashMap, [createHashMap]]];

        private _defaults = _self call ["defaults", []];

        {
            if !(_x in _actor) then { _actor set [_x, _y]; };
        } forEach _defaults;

        _actor
    }],
    ["validate", compileFinal {
        params [["_actor", createHashMap, [createHashMap]]];

        private _uid = _actor getOrDefault ["uid", ""];
        private _name = _actor getOrDefault ["name", ""];
        private _position = _actor getOrDefault ["position", []];
        private _direction = _actor getOrDefault ["direction", 0];
        private _stance = _actor getOrDefault ["stance", ""];
        private _rank = _actor getOrDefault ["rank", ""];
        private _state = _actor getOrDefault ["state", ""];
        private _phone_number = _actor getOrDefault ["phone_number", ""];
        private _email = _actor getOrDefault ["email", ""];
        private _organization = _actor getOrDefault ["organization", ""];

        [_uid, _name, _position, _direction, _stance, _rank, _state, _phone_number, _email, _organization] try {
            if (_uid isEqualTo "" || !(_uid isEqualType "")) then { throw "Invalid UID!"; };
            if (_name isEqualTo "" || !(_name isEqualType "")) then { throw "Invalid Name!"; };
            if (_position isEqualTo [] || !(_position isEqualType [])) then { throw "Invalid Position!"; };
            if (_direction < 0 || !(_direction isEqualType 0)) then { throw "Invalid Direction!"; };
            if (_stance isEqualTo "" || !(_stance isEqualType "")) then { throw "Invalid Stance!"; };
            if (_rank isEqualTo "" || !(_rank isEqualType "")) then { throw "Invalid Rank!"; };
            if (_state isEqualTo "" || !(_state isEqualType "")) then { throw "Invalid State!"; };
            if (_phone_number isEqualTo "" || !(_phone_number isEqualType "")) then { throw "Invalid Phone Number!"; };
            if (_email isEqualTo "" || !(_email isEqualType "")) then { throw "Invalid Email!"; };
            if (_organization isEqualTo "" || !(_organization isEqualType "")) then { throw "Invalid Organization!"; };
        } catch {
            ["ERROR", format ["Failed to validate actor %1!", _exception]] call EFUNC(common,log);
            false
        };

        true
    }]
]];

GVAR(ActorBaseStore) = compileFinal createHashMapFromArray [
    ["#base", EGVAR(common,BaseStore)],
    ["#type", "ActorBaseStore"],
    ["#create", compileFinal {
        ["INFO", "Actor Store Initialized!"] call EFUNC(common,log);
    }],
    ["cacheActor", compileFinal {
        params [["_uid", "", [""]], ["_actor", createHashMap, [createHashMap]]];

        if (_uid isEqualTo "" || { !(_actor isEqualType createHashMap) }) exitWith { createHashMap };

        GVAR(ActorModel) call ["migrate", [+_actor]]
    }],
    ["callHotActor", compileFinal {
        params [["_function", "", [""]], ["_arguments", [], [[]]]];

        if (_function isEqualTo "") exitWith { createHashMap };

        [_function, _arguments] call EFUNC(extension,extCall) params ["_result", "_isSuccess"];
        if !(_isSuccess) exitWith { createHashMap };
        if !(_result isEqualType "") exitWith { createHashMap };
        if ((_result find "Error:") == 0) exitWith {
            ["ERROR", format ["Actor extension call '%1' failed: %2", _function, _result]] call EFUNC(common,log);
            createHashMap
        };

        private _data = fromJSON _result;
        if !(_data isEqualType createHashMap) exitWith { createHashMap };
        _data
    }],
    ["listHotUids", compileFinal {
        ["actor:hot:keys", []] call EFUNC(extension,extCall) params ["_result", "_isSuccess"];
        if !(_isSuccess) exitWith { [] };
        if !(_result isEqualType "") exitWith { [] };
        if ((_result find "Error:") == 0) exitWith {
            ["ERROR", format ["Actor extension call '%1' failed: %2", "actor:hot:keys", _result]] call EFUNC(common,log);
            []
        };

        private _uids = fromJSON _result;
        if !(_uids isEqualType []) exitWith { [] };

        _uids select { _x isEqualType "" && { _x isNotEqualTo "" } }
    }],
    ["loadHotActor", compileFinal {
        params [["_uid", "", [""]], ["_initialize", false, [false]]];

        if (_uid isEqualTo "") exitWith { createHashMap };
        if (_initialize) then {
            // Missing actors should be created explicitly from a server snapshot
            // before the hot cache is initialized.
            private _ensureResult = _self call ["ensurePersistentActor", [_uid]];
            if !(_ensureResult isEqualType true && { _ensureResult }) exitWith { createHashMap };
        };

        private _command = ["actor:hot:get", "actor:hot:init"] select _initialize;
        private _actor = _self call ["callHotActor", [_command, [_uid]]];
        if (_actor isEqualTo createHashMap) exitWith { _actor };

        _self call ["hydrateActorIfNeeded", [_uid, _actor, true]]
    }],
    ["ensurePersistentActor", compileFinal {
        params [["_uid", "", [""]]];

        if (_uid isEqualTo "") exitWith { false };

        ["actor:exists", [_uid]] call EFUNC(extension,extCall) params ["_existsResult", "_existsSuccess"];
        if (!_existsSuccess || { !(_existsResult isEqualType "") }) exitWith {
            ["ERROR", format ["Failed to verify persistent actor state for %1.", _uid]] call EFUNC(common,log);
            false
        };

        if (_existsResult isEqualTo "true") exitWith { true };

        private _player = [_uid] call EFUNC(common,getPlayer);
        private _actor = GVAR(ActorModel) call ["fromPlayer", [_player]];
        _actor set ["uid", _uid];

        if ((_actor getOrDefault ["organization", ""]) isEqualTo "") then {
            _actor set ["organization", "default"];
        };

        private _json = _self call ["toJSON", [_actor]];
        ["actor:create", [_uid, _json]] call EFUNC(extension,extCall) params ["_createResult", "_createSuccess"];

        if (!_createSuccess || { !(_createResult isEqualType "") }) exitWith {
            ["ERROR", format ["Failed to create actor %1 from server snapshot.", _uid]] call EFUNC(common,log);
            false
        };

        if ((_createResult find "Error:") == 0) exitWith {
            ["ERROR", format ["Actor create for %1 failed: %2", _uid, _createResult]] call EFUNC(common,log);
            false
        };

        true
    }],
    ["hydrateActorIfNeeded", compileFinal {
        params [["_uid", "", [""]], ["_actor", createHashMap, [createHashMap]], ["_save", true, [false]]];

        if (_uid isEqualTo "" || { !(_actor isEqualType createHashMap) } || { _actor isEqualTo createHashMap }) exitWith {
            createHashMap
        };

        // Hot actor reads can still surface older partial records. Repair them
        // from the live player snapshot when possible and persist the result.
        private _hydratedActor = GVAR(ActorModel) call ["migrate", [+_actor]];
        private _defaults = GVAR(ActorModel) call ["defaults", []];
        private _player = [_uid] call EFUNC(common,getPlayer);
        private _needsPersist = false;

        if ((_hydratedActor getOrDefault ["uid", ""]) isEqualTo "") then {
            _hydratedActor set ["uid", _uid];
            _needsPersist = true;
        };
        if ((_hydratedActor getOrDefault ["organization", ""]) isEqualTo "") then {
            _hydratedActor set ["organization", "default"];
            _needsPersist = true;
        };

        {
            private _value = _hydratedActor getOrDefault [_x, ""];
            if !(_value isEqualType "") then {
                _hydratedActor set [_x, _defaults getOrDefault [_x, ""]];
                _needsPersist = true;
            };
        } forEach ["phone_number", "email"];

        if (_player isNotEqualTo objNull) then {
            private _snapshot = GVAR(ActorModel) call ["fromPlayer", [_player]];
            private _name = _hydratedActor getOrDefault ["name", ""];
            if (
                !(_name isEqualType "")
                || { _name isEqualTo "" }
                || { toLowerANSI _name isEqualTo "unknown" }
            ) then {
                _hydratedActor set ["name", _snapshot getOrDefault ["name", name _player]];
                _needsPersist = true;
            };

            private _position = _hydratedActor getOrDefault ["position", []];
            if !(_position isEqualType [] && { count _position isEqualTo 3 }) then {
                _hydratedActor set ["position", _snapshot getOrDefault ["position", getPosASL _player]];
                _needsPersist = true;
            };

            private _direction = _hydratedActor getOrDefault ["direction", 0];
            if !(_direction isEqualType 0) then {
                _hydratedActor set ["direction", _snapshot getOrDefault ["direction", getDir _player]];
                _needsPersist = true;
            };

            {
                private _fieldValue = _hydratedActor getOrDefault [_x, ""];
                if (!(_fieldValue isEqualType "") || { _fieldValue isEqualTo "" }) then {
                    _hydratedActor set [_x, _snapshot getOrDefault [_x, _defaults getOrDefault [_x, ""]]];
                    _needsPersist = true;
                };
            } forEach ["stance", "rank", "state"];

            private _loadout = _hydratedActor getOrDefault ["loadout", []];
            if !(_loadout isEqualType [] && { count _loadout > 0 }) then {
                _hydratedActor set ["loadout", getUnitLoadout _player];
                _needsPersist = true;
            };
        } else {
            {
                private _fieldValue = _hydratedActor getOrDefault [_x, ""];
                if (!(_fieldValue isEqualType "") || { _fieldValue isEqualTo "" }) then {
                    _hydratedActor set [_x, _defaults getOrDefault [_x, ""]];
                    _needsPersist = true;
                };
            } forEach ["stance", "rank", "state"];
        };

        if !_needsPersist exitWith {
            _self call ["cacheActor", [_uid, _hydratedActor]]
        };

        private _updatedActor = _self call ["override", [_uid, _hydratedActor, _save]];
        if (_updatedActor isEqualType createHashMap && { _updatedActor isNotEqualTo createHashMap }) exitWith {
            _self call ["cacheActor", [_uid, _updatedActor]]
        };

        ["WARNING", format ["Failed to hydrate actor %1 from player snapshot.", _uid]] call EFUNC(common,log);
        _self call ["cacheActor", [_uid, _hydratedActor]]
    }],
    ["init", compileFinal {
        params [["_uid", "", [""]]];

        private _player = [_uid] call EFUNC(common,getPlayer);

        ["actor:exists", [_uid]] call EFUNC(extension,extCall) params ["_result", "_isSuccess"];
        if !(_isSuccess) exitWith {
            ["ERROR", format ["Failed to check if actor %1 exists! Using fallback actor.", _uid]] call EFUNC(common,log);

            private _fallbackActor = GVAR(ActorModel) call ["fromPlayer", [_player]];
            _fallbackActor set ["uid", _uid];
            _fallbackActor = _self call ["cacheActor", [_uid, _fallbackActor]];

            [CRPC(actor,responseInitActor), [_fallbackActor], _player] call CFUNC(targetEvent);
            _fallbackActor
        };

        private _finalActor = createHashMap;
        if (_result == "true") then {
            _finalActor = _self call ["loadHotActor", [_uid, true]];
            ["INFO", format ["Found actor for %1", _uid]] call EFUNC(common,log);
        } else {
            if !(_self call ["ensurePersistentActor", [_uid]]) exitWith {
                ["ERROR", format ["Failed to create actor %1! Using fallback actor.", _uid]] call EFUNC(common,log);

                _finalActor = GVAR(ActorModel) call ["fromPlayer", [_player]];
                _finalActor set ["uid", _uid];
                _finalActor = _self call ["cacheActor", [_uid, _finalActor]];
                [CRPC(actor,responseInitActor), [_finalActor], _player] call CFUNC(targetEvent);
                _finalActor
            };

            _finalActor = _self call ["loadHotActor", [_uid, true]];
            ["INFO", format ["Created new actor for %1", _uid]] call EFUNC(common,log);
        };

        if (_finalActor isEqualTo createHashMap) then {
            _finalActor = GVAR(ActorModel) call ["fromPlayer", [_player]];
            _finalActor set ["uid", _uid];
        };

        _finalActor = _self call ["cacheActor", [_uid, _finalActor]];

        [CRPC(actor,responseInitActor), [_finalActor], _player] call CFUNC(targetEvent);
        _finalActor
    }],
    ["get", compileFinal {
        params [["_uid", "", [""]], ["_field", "", [""]]];

        private _actor = _self call ["loadHotActor", [_uid, false]];

        if (_field isEqualTo "") exitWith { _actor };
        _actor getOrDefault [_field, nil]
    }],
    ["load", compileFinal {
        params [["_uid", "", [""]]];

        private _actor = _self call ["get", [_uid, ""]];
        if !(_actor isEqualType createHashMap) exitWith { createHashMap };

        _actor
    }],
    ["getFieldOrDefault", compileFinal {
        params [["_uid", "", [""]], ["_field", "", [""]], ["_default", nil]];

        if (_uid isEqualTo "" || { _field isEqualTo "" }) exitWith { _default };

        private _actor = _self call ["load", [_uid]];
        if !(_actor isEqualType createHashMap) exitWith { _default };
        if (_actor isEqualTo createHashMap) exitWith { _default };

        _actor getOrDefault [_field, _default]
    }],
    ["getOrganization", compileFinal {
        params [["_uid", "", [""]], ["_default", "default", [""]]];

        private _orgID = _self call ["getFieldOrDefault", [_uid, "organization", _default]];
        if !(_orgID isEqualType "") exitWith { _default };
        if (_orgID isEqualTo "") exitWith { _default };

        _orgID
    }],
    ["getName", compileFinal {
        params [["_uid", "", [""]], ["_default", "", [""]]];

        private _name = _self call ["getFieldOrDefault", [_uid, "name", _default]];
        if !(_name isEqualType "") exitWith { _default };

        _name
    }],
    ["getPhoneNumber", compileFinal {
        params [["_uid", "", [""]], ["_default", "", [""]]];

        private _phoneNumber = _self call ["getFieldOrDefault", [_uid, "phone_number", _default]];
        if !(_phoneNumber isEqualType "") exitWith { _default };

        _phoneNumber
    }],
    ["override", compileFinal {
        params [
            ["_uid", "", [""]],
            ["_data", createHashMap, [createHashMap]],
            ["_save", false, [false]]
        ];

        if (_uid isEqualTo "" || { !(_data isEqualType createHashMap) }) exitWith { createHashMap };

        private _actor = _self call ["callHotActor", ["actor:hot:override", [_uid, toJSON _data]]];
        if (_save && { _actor isNotEqualTo createHashMap }) then {
            private _savedActor = _self call ["callHotActor", ["actor:hot:save", [_uid]]];
            if (_savedActor isNotEqualTo createHashMap) then {
                _actor = _savedActor;
            } else {
                _actor = createHashMap;
            };
        };

        if (_actor isEqualTo createHashMap) exitWith { _actor };
        _self call ["cacheActor", [_uid, _actor]]
    }],
    ["set", compileFinal {
        params [
            ["_uid", "", [""]],
            ["_field", "", [""]],
            ["_value", nil, [0, "", [], false, createHashMap, objNull, grpNull]],
            ["_sync", false, [false]]
        ];

        if (_uid isEqualTo "" || { _field isEqualTo "" }) exitWith { createHashMap };

        private _actor = _self call ["get", [_uid, ""]];
        if !(_actor isEqualType createHashMap) exitWith { createHashMap };

        _actor set [_field, _value];
        private _updatedActor = _self call ["override", [_uid, _actor, _sync]];
        if !(_updatedActor isEqualType createHashMap) exitWith { createHashMap };
        if (_updatedActor isEqualTo createHashMap) exitWith { createHashMap };

        createHashMapFromArray [[_field, _updatedActor getOrDefault [_field, _value]]]
    }],
    ["mset", compileFinal {
        params [["_uid", "", [""]], ["_fieldValuePairs", createHashMap, [createHashMap]], ["_sync", false, [false]]];

        if (_uid isEqualTo "" || { !(_fieldValuePairs isEqualType createHashMap) }) exitWith { createHashMap };

        private _actor = _self call ["get", [_uid, ""]];
        if !(_actor isEqualType createHashMap) exitWith { createHashMap };

        { _actor set [_x, _y]; } forEach _fieldValuePairs;
        private _updatedActor = _self call ["override", [_uid, _actor, _sync]];
        if !(_updatedActor isEqualType createHashMap) exitWith { createHashMap };
        if (_updatedActor isEqualTo createHashMap) exitWith { createHashMap };

        +_fieldValuePairs
    }],
    ["save", compileFinal {
        params [["_uid", "", [""]]];

        if (_uid isEqualTo "") exitWith { createHashMap };
        private _actor = _self call ["callHotActor", ["actor:hot:save", [_uid]]];
        if (_actor isEqualTo createHashMap) exitWith { _actor };

        _self call ["cacheActor", [_uid, _actor]]
    }],
    ["remove", compileFinal {
        params [["_uid", "", [""]]];

        if (_uid isEqualTo "") exitWith { false };

        ["actor:hot:remove", [_uid]] call EFUNC(extension,extCall) params ["_result", "_isSuccess"];
        _isSuccess && { _result isEqualTo "OK" }
    }],
    ["snapshot", compileFinal {
        params [["_uid", "", [""]]];

        private _player = [_uid] call EFUNC(common,getPlayer);
        private _finalActor = +(_self call ["get", [_uid, ""]]);

        if (!(_finalActor isEqualType createHashMap) || (_finalActor isEqualTo createHashMap)) then {
            _finalActor = GVAR(ActorModel) call ["defaults", []];
            _finalActor set ["uid", _uid];
        };

        if (_player isNotEqualTo objNull) then {
            _finalActor set ["uid", _uid];
            _finalActor set ["name", name _player];
            _finalActor set ["position", getPosASL _player];
            _finalActor set ["direction", getDir _player];
            _finalActor set ["stance", stance _player];
            _finalActor set ["rank", rank _player];
            _finalActor set ["state", lifeState _player];
            _finalActor set ["loadout", getUnitLoadout _player];
            if ((_finalActor getOrDefault ["organization", ""]) isEqualTo "") then {
                _finalActor set ["organization", "default"];
            };
        } else {
            ["WARNING", format ["No player object found for %1 during actor snapshot, using cached values.", _uid]] call EFUNC(common,log);
        };

        _self call ["override", [_uid, _finalActor, false]]
    }]
];

GVAR(ActorStore) = createHashMapObject [GVAR(ActorBaseStore)];
GVAR(ActorStore)
