<?php

declare(strict_types=1);

require_once __DIR__ . '/libs/ShellyParser.php';

class Shelly_Parser_MQTT extends IPSModule
{
    private bool $Subscribed = false;

    public function Create()
    {
        parent::Create();

        // MQTT Parent
        $this->ConnectParent('{C6D2AEB3-6E1F-4B2E-8E69-3A1A00246850}');
    }

    public function ApplyChanges()
    {
        parent::ApplyChanges();

        $this->ConnectParent('{C6D2AEB3-6E1F-4B2E-8E69-3A1A00246850}');

        // Eines für ALLE Outputs
        $this->EnsureActionScript();

        $this->Subscribed = false;
    }

    /* ---------------------------------------------------------
     * DEBUG
     * ---------------------------------------------------------*/
    private function Log($title, $msg)
    {
        $this->SendDebug($title, $msg, 0);
        IPS_LogMessage("ShellyMQTT/$title", $msg);
    }

    /* ---------------------------------------------------------
     * SAFE SEND (Parent prüfen)
     * ---------------------------------------------------------*/
    private function SafeSend(array $packet)
    {
        $parent = @IPS_GetInstance($this->InstanceID)['ConnectionID'] ?? 0;

        if ($parent === 0 || !IPS_InstanceExists($parent)) return;
        if (IPS_GetInstance($parent)['InstanceStatus'] !== 102) return;

        @$this->SendDataToParent(json_encode($packet));
    }

    /* ---------------------------------------------------------
     * SUBSCRIBE
     * ---------------------------------------------------------*/
    private function EnsureSubscribe()
    {
        if ($this->Subscribed) return;
        $this->Subscribed = true;

        $this->SafeSend([
            'DataID'           => '{043EA491-0325-4ADD-8FC2-A30C8EEB4D3F}',
            'PacketType'       => 8,
            'QualityOfService' => 0,
            'Retain'           => false,
            'Topic'            => '#',
            'Payload'          => ''
        ]);

        $this->Log("Subscribe", "#");
    }

    /* ---------------------------------------------------------
     * PUBLISH
     * ---------------------------------------------------------*/
    private function Publish(string $topic, string $payload)
    {
        $this->SafeSend([
            'DataID'           => '{043EA491-0325-4ADD-8FC2-A30C8EEB4D3F}',
            'PacketType'       => 3,
            'QualityOfService' => 0,
            'Retain'           => false,
            'Topic'            => $topic,
            'Payload'          => $payload
        ]);

        $this->Log("Publish", "$topic → $payload");
    }

    /* ---------------------------------------------------------
     * REQUEST ACTION
     * ---------------------------------------------------------*/
    public function RequestAction($Ident, $Value)
    {
        $this->Log("RequestAction", "$Ident → " . json_encode($Value));

        if (!str_contains($Ident, '_output_'))
            throw new Exception("Unknown Ident: $Ident");

        // Variable setzen
        $varID = $this->FindVariableByIdent($Ident);
        if ($varID) SetValue($varID, $Value);

        // deviceID / index extrahieren
        [$deviceID, $suffix] = explode('_output_', $Ident);
        $index = intval($suffix);

        $topic = "$deviceID/rpc";

        $payload = json_encode([
            'id'     => 1,
            'src'    => 'ips',
            'method' => 'Switch.Set',
            'params' => [
                'id' => $index,
                'on' => (bool)$Value
            ]
        ]);

        $this->Publish($topic, $payload);
    }

    /* ---------------------------------------------------------
     * RECEIVE DATA
     * ---------------------------------------------------------*/
    public function ReceiveData($JSONString)
    {
        $this->EnsureSubscribe();

        $data = json_decode($JSONString, true);
        if (!is_array($data)) return;

        $topic   = $data['Topic']   ?? '';
        $payload = $data['Payload'] ?? '';

        if ($topic === '') return;

        $this->Log("ReceiveTopic", "$topic → $payload");

        $parts    = explode('/', $topic);
        $deviceID = $parts[0] ?? '';

        if ($deviceID === '') return;

        // ONLINE
        if (($parts[1] ?? '') === 'online') {
            $this->HandleOnline($deviceID, $payload);
            return;
        }

        // RPC
        if (($parts[1] ?? '') === 'events' && ($parts[2] ?? '') === 'rpc') {
            $this->HandleRPC($deviceID, $payload);
            return;
        }
    }

    /* ---------------------------------------------------------
     * ONLINE
     * ---------------------------------------------------------*/
    private function HandleOnline(string $deviceID, string $payload)
    {
        $value = ($payload === 'true' || $payload === '1');
        $vid = $this->EnsureBooleanVariable($deviceID, $deviceID . '_online', 'Online');
        SetValue($vid, $value);
    }

    /* ---------------------------------------------------------
     * RPC / EVENTS
     * ---------------------------------------------------------*/
    private function HandleRPC(string $deviceID, string $payload)
    {
        $json = json_decode($payload, true);
        if (!is_array($json)) return;

        $src = $json['src'] ?? '';
        if (!str_starts_with($src, 'shelly')) return;

        $type = ShellyParser::ExtractType($src);
        $tid  = $this->EnsureStringVariable($deviceID, "{$deviceID}_type", "Typ");
        SetValue($tid, $type);

        $params = $json['params'] ?? [];
        $mapped = ShellyParser::MapParams($params);

        foreach ($mapped['outputs'] as $i => $val) {
            $vid = $this->EnsureBooleanVariable($deviceID, "{$deviceID}_output_$i", "Output $i");
            SetValue($vid, $val);
        }

        foreach ($mapped['inputs'] as $i => $val) {
            $vid = $this->EnsureBooleanVariable($deviceID, "{$deviceID}_input_$i", "Input $i");
            SetValue($vid, $val);
        }

        if ($mapped['temperature'] !== null) {
            $f = $this->EnsureFloatVariable($deviceID, "{$deviceID}_temperature", "Temperatur");
            SetValue($f, $mapped['temperature']);
        }
    }

    /* ---------------------------------------------------------
     * ACTION SCRIPT (einmalig für ALLE Outputs)
     * ---------------------------------------------------------*/
    private function EnsureActionScript()
    {
        $ident = "action_handler";

        $sid = @IPS_GetObjectIDByIdent($ident, $this->InstanceID);
        if ($sid === false) {

            $sid = IPS_CreateScript(0);
            IPS_SetParent($sid, $this->InstanceID);
            IPS_SetIdent($sid, $ident);
            IPS_SetName($sid, "Shelly Action Handler");

            $code = <<<'EOF'
    <?php

    $moduleID = IPS_GetParent($_IPS['SELF']);
    $varID    = $_IPS['VARIABLE'];
    $value    = $_IPS['VALUE'];

    $ident = IPS_GetObject($varID)['ObjectIdent'];

    IPS_RequestAction($moduleID, $ident, $value);

    ?>
    EOF;

            IPS_SetScriptContent($sid, $code);
        }

        return $sid;
    }


    /* ---------------------------------------------------------
     * VARIABLEN
     * ---------------------------------------------------------*/
    private function EnsureBooleanVariable(string $deviceID, string $ident, string $name): int
    {
        $folder = $this->GetDeviceFolder($deviceID);
        $vid = 0;

        foreach (IPS_GetChildrenIDs($folder) as $cid) {
            $obj = IPS_GetObject($cid);

            // AutoFix alter Versionen ohne Ident
            if ($obj['ObjectName'] === $name && $obj['ObjectIdent'] === '') {
                IPS_SetIdent($cid, $ident);
                $vid = $cid;
                break;
            }

            if ($obj['ObjectIdent'] === $ident) {
                $vid = $cid;
                break;
            }
        }

        if ($vid === 0) {
            $vid = IPS_CreateVariable(0);
            IPS_SetName($vid, $name);
            IPS_SetIdent($vid, $ident);
            IPS_SetParent($vid, $folder);
        }

        // OUTPUT → EIN gemeinsames Action Script
        if (str_contains($ident, '_output_')) {
            IPS_SetVariableCustomProfile($vid, '~Switch');
            IPS_SetVariableCustomAction($vid, $this->EnsureActionScript());
        }

        return $vid;
    }

    private function EnsureFloatVariable(string $deviceID, string $ident, string $name): int
    {
        $folder = $this->GetDeviceFolder($deviceID);

        foreach (IPS_GetChildrenIDs($folder) as $cid) {
            if (IPS_GetObject($cid)['ObjectIdent'] === $ident)
                return $cid;
        }

        $vid = $this->RegisterVariableFloat($ident, $name);
        IPS_SetParent($vid, $folder);
        return $vid;
    }

    private function EnsureStringVariable(string $deviceID, string $ident, string $name): int
    {
        $folder = $this->GetDeviceFolder($deviceID);

        foreach (IPS_GetChildrenIDs($folder) as $cid) {
            if (IPS_GetObject($cid)['ObjectIdent'] === $ident)
                return $cid;
        }

        $vid = $this->RegisterVariableString($ident, $name);
        IPS_SetParent($vid, $folder);
        return $vid;
    }

    /* ---------------------------------------------------------
     * DEVICE FOLDER
     * ---------------------------------------------------------*/
    private function GetDeviceFolder(string $deviceID): int
    {
        $ident = "folder_" . $deviceID;

        foreach (IPS_GetChildrenIDs($this->InstanceID) as $cid) {
            if (IPS_GetObject($cid)['ObjectIdent'] === $ident)
                return $cid;
        }

        $fid = IPS_CreateCategory();
        IPS_SetName($fid, $deviceID);
        IPS_SetIdent($fid, $ident);
        IPS_SetParent($fid, $this->InstanceID);

        return $fid;
    }

    private function FindVariableByIdent(string $Ident): int
    {
        foreach (IPS_GetChildrenIDs($this->InstanceID) as $folder) {
            foreach (IPS_GetChildrenIDs($folder) as $cid) {
                if (IPS_GetObject($cid)['ObjectIdent'] === $Ident)
                    return $cid;
            }
        }
        return 0;
    }
}

?>
