// Stargate Update Agent v1
// Install this script in each updatable in-world object (gate, DHD, etc.).
// It arms a short-lived script access pin so a trusted updater can push scripts.

integer UPD_CHANNEL = -778880;
string AGENT_VERSION = "1.0.0";

// Set per object: GATE, DHD, BRIDGE, or CUSTOM
string ROLE = "GATE";

integer PIN_TTL_SECONDS = 30;
integer debug = FALSE;

integer listenHandle = 0;
integer armedPin = 0;
integer pinExpiresUnix = 0;

say_debug(string msg)
{
    if (debug) llOwnerSay("[UPD_AGENT] " + msg);
}

string upper_trim(string s)
{
    return llToUpper(llStringTrim(s, STRING_TRIM));
}

integer is_owner_match(string ownerKey)
{
    return ((key)ownerKey == llGetOwner());
}

integer is_same_owner_speaker(key speaker)
{
    return (llGetOwnerKey(speaker) == llGetOwner());
}

arm_pin(integer pin)
{
    if (pin <= 0) return;
    armedPin = pin;
    pinExpiresUnix = llGetUnixTime() + PIN_TTL_SECONDS;
    llSetRemoteScriptAccessPin(armedPin);
    say_debug("Pin armed until " + (string)pinExpiresUnix);
}

disarm_pin()
{
    armedPin = 0;
    pinExpiresUnix = 0;
    llSetRemoteScriptAccessPin(0);
    say_debug("Pin disarmed.");
}

handle_ping(string nonce)
{
    llRegionSay(UPD_CHANNEL,
        "SG_UPD|PONG|" + nonce + "|" +
        (string)llGetKey() + "|" +
        llGetObjectName() + "|" +
        ROLE + "|" + AGENT_VERSION
    );
}

handle_prep(string nonce, string targetKey, string pinStr)
{
    if ((key)targetKey != llGetKey()) return;

    integer pin = (integer)llStringTrim(pinStr, STRING_TRIM);
    if (pin <= 0)
    {
        llRegionSay(UPD_CHANNEL, "SG_UPD|ERR|" + nonce + "|" + (string)llGetKey() + "|bad_pin");
        return;
    }

    arm_pin(pin);

    llRegionSay(UPD_CHANNEL,
        "SG_UPD|READY|" + nonce + "|" +
        (string)llGetKey() + "|" + ROLE + "|" + AGENT_VERSION
    );
}

handle_disarm(string targetKey)
{
    if ((key)targetKey != llGetKey()) return;
    disarm_pin();
}

default
{
    state_entry()
    {
        listenHandle = llListen(UPD_CHANNEL, "", NULL_KEY, "");
        disarm_pin();
        llSetTimerEvent(2.0);
    }

    on_rez(integer start_param)
    {
        llResetScript();
    }

    timer()
    {
        if (armedPin > 0 && llGetUnixTime() >= pinExpiresUnix)
        {
            disarm_pin();
        }
    }

    listen(integer channel, string name, key id, string msg)
    {
        if (channel != UPD_CHANNEL) return;
        if (!is_same_owner_speaker(id)) return;

        list p = llParseStringKeepNulls(msg, ["|"], []);
        string sig = upper_trim(llList2String(p, 0));
        if (sig != "SG_UPD") return;

        string cmd = upper_trim(llList2String(p, 1));
        string nonce = llStringTrim(llList2String(p, 2), STRING_TRIM);

        if (cmd == "PING")
        {
            string ownerKey = llStringTrim(llList2String(p, 3), STRING_TRIM);
            if (is_owner_match(ownerKey)) handle_ping(nonce);
            return;
        }

        if (cmd == "PREP")
        {
            string targetKey = llStringTrim(llList2String(p, 3), STRING_TRIM);
            string pinStr = llStringTrim(llList2String(p, 4), STRING_TRIM);
            string ownerKey2 = llStringTrim(llList2String(p, 5), STRING_TRIM);
            if (is_owner_match(ownerKey2)) handle_prep(nonce, targetKey, pinStr);
            return;
        }

        if (cmd == "DISARM")
        {
            string targetKey2 = llStringTrim(llList2String(p, 3), STRING_TRIM);
            string ownerKey3 = llStringTrim(llList2String(p, 4), STRING_TRIM);
            if (is_owner_match(ownerKey3)) handle_disarm(targetKey2);
            return;
        }
    }

    changed(integer change)
    {
        if (change & CHANGED_OWNER)
        {
            llResetScript();
        }
    }
}
