<?php

namespace bird\helpers;

interface bird_payload_i
{
    public static function fromArray(array $payload): self;

    public function toArray(): array;
}

abstract class bird_payload implements bird_payload_i
{
    protected array $payload;

    final public function __construct(array $payload)
    {
        $this->payload = $payload;
    }

    final public function toArray(): array
    {
        return $this->payload;
    }

    protected static function pickAllowedKeys(array $payload, array $allowedKeys): array
    {
        $out = [];
        foreach ($allowedKeys as $key) {
            if (array_key_exists($key, $payload)) {
                $out[$key] = $payload[$key];
            }
        }
        return $out;
    }

    protected static function normalizeIntegerFields(array $payload, array $fields): array
    {
        foreach ($fields as $field) {
            if (!array_key_exists($field, $payload)) {
                continue;
            }
            $intValue = self::toInt($payload[$field]);
            if ($intValue !== null) {
                $payload[$field] = $intValue;
            }
        }
        return $payload;
    }

    protected static function normalizeBooleanFields(array $payload, array $fields): array
    {
        foreach ($fields as $field) {
            if (!array_key_exists($field, $payload)) {
                continue;
            }
            $boolValue = self::toBool($payload[$field]);
            if ($boolValue !== null) {
                $payload[$field] = $boolValue;
            }
        }
        return $payload;
    }

    protected static function normalizeStringListField(array $payload, string $field, bool $splitCsv = false): array
    {
        if (!array_key_exists($field, $payload)) {
            return $payload;
        }

        $value = $payload[$field];
        $source = [];
        if (is_array($value)) {
            $source = $value;
        } elseif (is_string($value) && $splitCsv) {
            $source = explode(',', $value);
        } else {
            return $payload;
        }

        $out = [];
        foreach ($source as $item) {
            if (!is_scalar($item)) {
                continue;
            }
            $normalized = trim((string)$item);
            if ($normalized !== '') {
                $out[] = $normalized;
            }
        }

        $payload[$field] = array_values($out);
        return $payload;
    }

    protected static function normalizeCsvStringListField(array $payload, string $field): array
    {
        return self::normalizeStringListField($payload, $field, true);
    }

    protected static function normalizeCallFlow(array $payload): array
    {
        if (!array_key_exists('callFlow', $payload) || !is_array($payload['callFlow'])) {
            return $payload;
        }

        $commands = [];
        foreach ($payload['callFlow'] as $command) {
            if (!is_array($command)) {
                continue;
            }
            $normalizedCommand = self::pickAllowedKeys($command, ['command', 'conditions', 'options']);

            if (array_key_exists('conditions', $normalizedCommand) && is_array($normalizedCommand['conditions'])) {
                $conditions = [];
                foreach ($normalizedCommand['conditions'] as $condition) {
                    if (!is_array($condition)) {
                        continue;
                    }
                    $conditions[] = self::pickAllowedKeys($condition, ['variable', 'operator', 'value']);
                }
                $normalizedCommand['conditions'] = array_values($conditions);
            }

            if (array_key_exists('options', $normalizedCommand) && !is_array($normalizedCommand['options'])) {
                unset($normalizedCommand['options']);
            }

            $commands[] = $normalizedCommand;
        }

        $payload['callFlow'] = array_values($commands);
        return $payload;
    }

    protected static function normalizeNotification(array $payload): array
    {
        if (!array_key_exists('notification', $payload)) {
            return $payload;
        }
        if (!is_array($payload['notification'])) {
            unset($payload['notification']);
            return $payload;
        }

        $payload['notification'] = self::pickAllowedKeys($payload['notification'], ['url']);
        return $payload;
    }

    protected static function normalizeAmdSettings(array $payload): array
    {
        if (!array_key_exists('amdSettings', $payload)) {
            return $payload;
        }
        if (!is_array($payload['amdSettings'])) {
            unset($payload['amdSettings']);
            return $payload;
        }

        $amdSettings = self::pickAllowedKeys($payload['amdSettings'], [
            'enabled',
            'wordCount',
            'speechTimeout',
            'speechLocale',
            'beepTimeout',
            'ifMachineNotifyAfter',
        ]);
        $amdSettings = self::normalizeBooleanFields($amdSettings, ['enabled']);
        $amdSettings = self::normalizeIntegerFields($amdSettings, ['wordCount', 'speechTimeout', 'beepTimeout']);

        $payload['amdSettings'] = $amdSettings;
        return $payload;
    }

    protected static function toInt(mixed $value): ?int
    {
        if (is_int($value)) {
            return $value;
        }
        if (is_string($value) && preg_match('/^-?[0-9]+$/', $value) === 1) {
            return (int)$value;
        }
        return null;
    }

    protected static function toBool(mixed $value): ?bool
    {
        if (is_bool($value)) {
            return $value;
        }
        if (is_int($value) && ($value === 0 || $value === 1)) {
            return $value === 1;
        }
        if (!is_string($value)) {
            return null;
        }

        $normalized = strtolower(trim($value));
        if ($normalized === 'true' || $normalized === '1') {
            return true;
        }
        if ($normalized === 'false' || $normalized === '0') {
            return false;
        }
        return null;
    }
}

final class bird_no_body_payload extends bird_payload
{
    public static function fromArray(array $payload): self
    {
        return new self([]);
    }
}

final class bird_voice_calls_log_query_payload extends bird_payload
{
    public static function fromArray(array $payload): self
    {
        $normalized = self::pickAllowedKeys($payload, [
            'limit',
            'pageToken',
            'startAt',
            'endAt',
            'channelId',
            'status',
            'type',
            'to',
            'from',
            'duration',
            'direction',
            'id',
            'tag',
        ]);
        $normalized = self::normalizeIntegerFields($normalized, ['limit', 'duration']);
        $normalized = self::normalizeCsvStringListField($normalized, 'channelId');
        $normalized = self::normalizeCsvStringListField($normalized, 'tag');

        return new self($normalized);
    }
}

final class bird_voice_create_call_payload extends bird_payload
{
    public static function fromArray(array $payload): self
    {
        $normalized = self::pickAllowedKeys($payload, [
            'from',
            'to',
            'ringTimeout',
            'maxDuration',
            'sendKeys',
            'record',
            'recordStart',
            'flowStart',
            'stereo',
            'callFlow',
            'scheduledFor',
            'notification',
            'amdSettings',
            'tags',
        ]);
        $normalized = self::normalizeIntegerFields($normalized, ['ringTimeout', 'maxDuration']);
        $normalized = self::normalizeBooleanFields($normalized, ['record', 'stereo']);
        $normalized = self::normalizeStringListField($normalized, 'tags');
        $normalized = self::normalizeCallFlow($normalized);
        $normalized = self::normalizeNotification($normalized);
        $normalized = self::normalizeAmdSettings($normalized);

        return new self($normalized);
    }
}

final class bird_voice_list_calls_query_payload extends bird_payload
{
    public static function fromArray(array $payload): self
    {
        $normalized = self::pickAllowedKeys($payload, [
            'limit',
            'pageToken',
            'startAt',
            'endAt',
            'status',
            'type',
            'to',
            'from',
            'duration',
            'direction',
            'id',
            'tag',
        ]);
        $normalized = self::normalizeIntegerFields($normalized, ['limit', 'duration']);
        $normalized = self::normalizeCsvStringListField($normalized, 'tag');

        return new self($normalized);
    }
}

final class bird_voice_update_call_payload extends bird_payload
{
    public static function fromArray(array $payload): self
    {
        $normalized = self::pickAllowedKeys($payload, ['status', 'callFlow']);
        $normalized = self::normalizeCallFlow($normalized);

        return new self($normalized);
    }
}

final class bird_voice_hangup_payload extends bird_payload
{
    public static function fromArray(array $payload): self
    {
        return new self(self::pickAllowedKeys($payload, ['cause']));
    }
}

final class bird_voice_playback_payload extends bird_payload
{
    public static function fromArray(array $payload): self
    {
        $normalized = self::pickAllowedKeys($payload, ['media', 'loop', 'timeout', 'pauseMilliseconds']);
        $normalized = self::normalizeIntegerFields($normalized, ['loop', 'timeout', 'pauseMilliseconds']);
        $normalized = self::normalizeStringListField($normalized, 'media');

        return new self($normalized);
    }
}

final class bird_voice_say_payload extends bird_payload
{
    public static function fromArray(array $payload): self
    {
        $normalized = self::pickAllowedKeys($payload, ['text', 'locale', 'voice', 'loop', 'timeout', 'hangup']);
        $normalized = self::normalizeIntegerFields($normalized, ['loop', 'timeout']);
        $normalized = self::normalizeBooleanFields($normalized, ['hangup']);
        if (!array_key_exists('hangup', $normalized)) {
            $normalized['hangup'] = true;
        }

        return new self($normalized);
    }
}

final class bird_voice_gather_payload extends bird_payload
{
    public static function fromArray(array $payload): self
    {
        $normalized = self::pickAllowedKeys($payload, [
            'maxNumKeys',
            'endKey',
            'timeout',
            'retries',
            'input',
            'speechLocale',
            'playback',
            'say',
        ]);
        $normalized = self::normalizeIntegerFields($normalized, ['maxNumKeys', 'timeout', 'retries']);

        if (array_key_exists('playback', $normalized) && is_array($normalized['playback'])) {
            $normalized['playback'] = bird_voice_playback_payload::fromArray($normalized['playback'])->toArray();
        }

        if (array_key_exists('say', $normalized) && is_array($normalized['say'])) {
            $say = self::pickAllowedKeys($normalized['say'], ['text', 'locale', 'voice', 'loop', 'timeout', 'hangup']);
            $say = self::normalizeIntegerFields($say, ['loop', 'timeout']);
            $say = self::normalizeBooleanFields($say, ['hangup']);
            $normalized['say'] = $say;
        }

        return new self($normalized);
    }
}

final class bird_voice_bridge_payload extends bird_payload
{
    public static function fromArray(array $payload): self
    {
        $normalized = self::pickAllowedKeys($payload, [
            'from',
            'to',
            'ringTimeout',
            'maxDuration',
            'ringTone',
            'hangupAfterBridge',
            'record',
            'recordStart',
            'recordStereo',
            'callFlow',
            'notification',
            'amdSettings',
        ]);
        $normalized = self::normalizeIntegerFields($normalized, ['ringTimeout', 'maxDuration']);
        $normalized = self::normalizeBooleanFields($normalized, ['hangupAfterBridge', 'record', 'recordStereo']);
        $normalized = self::normalizeCallFlow($normalized);
        $normalized = self::normalizeNotification($normalized);
        $normalized = self::normalizeAmdSettings($normalized);

        return new self($normalized);
    }
}

final class bird_voice_record_payload extends bird_payload
{
    public static function fromArray(array $payload): self
    {
        $normalized = self::pickAllowedKeys($payload, ['endKey', 'maxLength', 'timeout', 'beep', 'transcribe', 'transcribeLocale']);
        $normalized = self::normalizeIntegerFields($normalized, ['maxLength', 'timeout']);
        $normalized = self::normalizeBooleanFields($normalized, ['beep', 'transcribe']);

        return new self($normalized);
    }
}

final class bird_voice_recordings_create_payload extends bird_payload
{
    public static function fromArray(array $payload): self
    {
        $normalized = self::pickAllowedKeys($payload, ['maxLength', 'stereo']);
        $normalized = self::normalizeIntegerFields($normalized, ['maxLength']);
        $normalized = self::normalizeBooleanFields($normalized, ['stereo']);

        return new self($normalized);
    }
}

final class bird_voice_recordings_list_query_payload extends bird_payload
{
    public static function fromArray(array $payload): self
    {
        $normalized = self::pickAllowedKeys($payload, ['limit', 'pageToken']);
        $normalized = self::normalizeIntegerFields($normalized, ['limit']);

        return new self($normalized);
    }
}

final class bird_voice_recording_update_payload extends bird_payload
{
    public static function fromArray(array $payload): self
    {
        return new self(self::pickAllowedKeys($payload, ['status']));
    }
}

final class bird_voice_test_outbound_payload extends bird_payload
{
    public static function fromArray(array $payload): self
    {
        $normalized = self::pickAllowedKeys($payload, [
            'from',
            'to',
            'ringTimeout',
            'maxDuration',
            'sendKeys',
            'record',
            'recordStart',
            'flowStart',
            'stereo',
            'callFlow',
            'scheduledFor',
            'notification',
            'amdSettings',
            'tags',
            'timeout',
            'pollIntervalSeconds',
            'maxPollSeconds',
            'hangupCause',
        ]);
        $normalized = self::normalizeIntegerFields($normalized, [
            'ringTimeout',
            'maxDuration',
            'timeout',
            'pollIntervalSeconds',
            'maxPollSeconds',
        ]);
        $normalized = self::normalizeBooleanFields($normalized, ['record', 'stereo']);
        $normalized = self::normalizeStringListField($normalized, 'tags');
        $normalized = self::normalizeCallFlow($normalized);
        $normalized = self::normalizeNotification($normalized);
        $normalized = self::normalizeAmdSettings($normalized);

        return new self($normalized);
    }
}

final class bird_flash_create_payload extends bird_payload
{
    public static function fromArray(array $payload): self
    {
        $normalized = self::pickAllowedKeys($payload, ['from', 'to', 'ringTimeout']);
        $normalized = self::normalizeIntegerFields($normalized, ['ringTimeout']);

        return new self($normalized);
    }
}

final class bird_flash_list_query_payload extends bird_payload
{
    public static function fromArray(array $payload): self
    {
        $normalized = self::pickAllowedKeys($payload, [
            'limit',
            'pageToken',
            'startAt',
            'endAt',
            'status',
            'to',
            'from',
            'duration',
            'id',
        ]);
        $normalized = self::normalizeIntegerFields($normalized, ['limit', 'duration']);

        return new self($normalized);
    }
}

final class bird_flash_end_payload extends bird_payload
{
    public static function fromArray(array $payload): self
    {
        return new self(self::pickAllowedKeys($payload, ['receivedCli', 'result']));
    }
}

final class bird_flash_hangup_payload extends bird_payload
{
    public static function fromArray(array $payload): self
    {
        return new self(self::pickAllowedKeys($payload, ['from', 'to', 'receivedCli', 'result']));
    }
}
