<?php

// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedNamespaceFound -- APD is the established namespace shared with the Pro add-on and retained for backward compatibility.
namespace APD\Support;

defined('ABSPATH') || exit;

final class LayoutStateConverter
{
    public static function stripLayoutFromState(mixed $state) : array
    {
        if (! is_array($state)) {
            return [];
        }

        $normalized = $state;

        unset($normalized['layout']);

        if (isset($normalized['ui']) && is_array($normalized['ui'])) {
            unset($normalized['ui']['layout']);
        }

        if (isset($normalized['legacy']) && is_array($normalized['legacy'])) {
            if (isset($normalized['legacy']['state']) && is_array($normalized['legacy']['state'])) {
                unset($normalized['legacy']['state']['layout']);
            }
            if (isset($normalized['legacy']['scope']) && is_array($normalized['legacy']['scope'])) {
                $normalized['legacy']['scope']['layout'] = false;
            }
        }

        if (! isset($normalized['scope']) || ! is_array($normalized['scope'])) {
            $normalized['scope'] = [];
        }
        $normalized['scope']['layout'] = false;

        return $normalized;
    }

    public static function extractLayoutConfigFromState(mixed $state, array $fallbackConfig = []) : array
    {
        if (! is_array($state)) {
            return self::normalizeConfigLayout($fallbackConfig);
        }

        $runtimeLayout = null;
        if (isset($state['layout']) && is_array($state['layout'])) {
            $runtimeLayout = $state['layout'];
        } elseif (isset($state['ui']) && is_array($state['ui']) && isset($state['ui']['layout']) && is_array($state['ui']['layout'])) {
            $runtimeLayout = $state['ui']['layout'];
        } elseif (
            isset($state['legacy']) && is_array($state['legacy'])
            && isset($state['legacy']['state']) && is_array($state['legacy']['state'])
            && isset($state['legacy']['state']['layout']) && is_array($state['legacy']['state']['layout'])
        ) {
            $runtimeLayout = $state['legacy']['state']['layout'];
        }

        if (is_array($runtimeLayout) && self::looksLikeRuntimeLayout($runtimeLayout)) {
            return self::runtimeLayoutToConfig($runtimeLayout, $fallbackConfig);
        }

        if (is_array($runtimeLayout) && self::looksLikeConfigLayout($runtimeLayout)) {
            return self::normalizeConfigLayout($runtimeLayout, $fallbackConfig);
        }

        return self::normalizeConfigLayout($fallbackConfig);
    }

    public static function runtimeLayoutToConfig(array $runtimeLayout, array $fallbackConfig = []) : array
    {
        $fallback = self::normalizeConfigLayout($fallbackConfig);
        $payload = [
            'height' => (string) ($fallback['height'] ?? '820px'),
        ];

        $blocksByZone = isset($runtimeLayout['blocksByZone']) && is_array($runtimeLayout['blocksByZone'])
            ? $runtimeLayout['blocksByZone']
            : [];
        $zoneCollapsed = isset($runtimeLayout['zoneCollapsed']) && is_array($runtimeLayout['zoneCollapsed'])
            ? $runtimeLayout['zoneCollapsed']
            : [];
        $zoneCollapsible = isset($runtimeLayout['zoneCollapsible']) && is_array($runtimeLayout['zoneCollapsible'])
            ? $runtimeLayout['zoneCollapsible']
            : [];
        $zoneType = isset($runtimeLayout['zoneType']) && is_array($runtimeLayout['zoneType'])
            ? $runtimeLayout['zoneType']
            : [];

        foreach (['left', 'right', 'top', 'bottom'] as $zone) {
            $fallbackZone = isset($fallback[$zone]) && is_array($fallback[$zone]) ? $fallback[$zone] : [];
            $items = [];
            foreach ((array) ($blocksByZone[$zone] ?? []) as $index => $item) {
                if (! is_array($item)) {
                    continue;
                }
                $normalizedItem = [
                    'id' => (string) ($item['id'] ?? ($item['kind'] ?? '')),
                    'kind' => (string) ($item['kind'] ?? ($item['id'] ?? '')),
                    'label' => (string) ($item['label'] ?? ($item['kind'] ?? ($item['id'] ?? ''))),
                    'icon' => self::normalizeIconName($item['icon'] ?? '', (string) ($item['kind'] ?? ($item['id'] ?? ''))),
                    'zone' => $zone,
                    'type' => (string) ($item['type'] ?? 'floating'),
                    'collapsed' => (bool) ($item['collapsed'] ?? false),
                    'collapsible' => array_key_exists('collapsible', $item) ? (bool) $item['collapsible'] : true,
                    'order' => $index,
                ];
                $mountTarget = self::normalizeMountTarget($item['mountTarget'] ?? '');
                if ($mountTarget !== '') {
                    $normalizedItem['mountTarget'] = $mountTarget;
                }
                if (($item['renderMode'] ?? null) === 'inline' || ($item['renderMode'] ?? null) === 'portal') {
                    $normalizedItem['renderMode'] = (string) $item['renderMode'];
                }
                if ($normalizedItem['id'] === '') {
                    $normalizedItem['id'] = $normalizedItem['kind'];
                }
                if ($normalizedItem['kind'] === '') {
                    $normalizedItem['kind'] = $normalizedItem['id'];
                }
                $items[] = $normalizedItem;
            }

            $payload[$zone] = [
                'items' => array_values($items),
                'collapsed' => array_key_exists($zone, $zoneCollapsed)
                    ? (bool) $zoneCollapsed[$zone]
                    : (array_key_exists('collapsed', $fallbackZone) ? (bool) $fallbackZone['collapsed'] : ($zone === 'top')),
                'collapsible' => array_key_exists($zone, $zoneCollapsible)
                    ? (bool) $zoneCollapsible[$zone]
                    : (array_key_exists('collapsible', $fallbackZone) ? (bool) $fallbackZone['collapsible'] : true),
                'type' => (string) ($fallbackZone['type'] ?? 'floating'),
            ];

            if (array_key_exists($zone, $zoneType)) {
                $mappedType = self::mapRuntimeZoneTypeToConfigType((string) $zoneType[$zone], (string) ($payload[$zone]['type'] ?? 'floating'), $zone);
                $payload[$zone]['type'] = $mappedType;
            }
        }

        return self::normalizeConfigLayout($payload, $fallback);
    }

    public static function normalizeConfigLayout(mixed $layout, array $fallbackConfig = []) : array
    {
        $layout = is_array($layout) ? $layout : [];
        $fallback = is_array($fallbackConfig) ? $fallbackConfig : [];
        $payload = [
            'height' => (string) ($layout['height'] ?? $fallback['height'] ?? '820px'),
        ];

        foreach (['left', 'right', 'top', 'bottom'] as $zone) {
            $zoneConfig = isset($layout[$zone]) && is_array($layout[$zone]) ? $layout[$zone] : [];
            $fallbackZone = isset($fallback[$zone]) && is_array($fallback[$zone]) ? $fallback[$zone] : [];
            $zoneItems = isset($zoneConfig['items']) && is_array($zoneConfig['items'])
                ? (array) $zoneConfig['items']
                : (self::isListArray($zoneConfig) ? $zoneConfig : []);
            $items = [];
            foreach ($zoneItems as $index => $item) {
                if (! is_array($item)) {
                    continue;
                }
                $id = (string) ($item['id'] ?? ($item['kind'] ?? ($item['module'] ?? '')));
                $kind = (string) ($item['kind'] ?? ($item['module'] ?? $id));
                if ($id === '') {
                    $id = $kind;
                }
                if ($kind === '') {
                    $kind = $id;
                }
                if ($id === '' || $kind === '') {
                    continue;
                }
                $normalizedItem = [
                    'id' => $id,
                    'kind' => $kind,
                    'label' => (string) ($item['label'] ?? $kind),
                    'icon' => self::normalizeIconName($item['icon'] ?? '', $kind),
                    'zone' => $zone,
                    'type' => (string) ($item['type'] ?? 'floating'),
                    'collapsed' => (bool) ($item['collapsed'] ?? false),
                    'collapsible' => array_key_exists('collapsible', $item) ? (bool) $item['collapsible'] : true,
                    'order' => self::readItemOrder($item, (int) $index),
                    '__sourceIndex' => (int) $index,
                ];
                $mountTarget = self::normalizeMountTarget($item['mountTarget'] ?? '');
                if ($mountTarget !== '') {
                    $normalizedItem['mountTarget'] = $mountTarget;
                }
                if (($item['renderMode'] ?? null) === 'inline' || ($item['renderMode'] ?? null) === 'portal') {
                    $normalizedItem['renderMode'] = (string) $item['renderMode'];
                }
                $items[] = $normalizedItem;
            }
            usort($items, static function (array $a, array $b) : int {
                $orderCompare = ((int) $a['order']) <=> ((int) $b['order']);
                if ($orderCompare !== 0) {
                    return $orderCompare;
                }
                return ((int) $a['__sourceIndex']) <=> ((int) $b['__sourceIndex']);
            });
            foreach ($items as $index => &$normalizedItem) {
                $normalizedItem['order'] = $index;
                unset($normalizedItem['__sourceIndex']);
            }
            unset($normalizedItem);
            $payload[$zone] = [
                'items' => array_values($items),
                'collapsed' => array_key_exists('collapsed', $zoneConfig)
                    ? (bool) $zoneConfig['collapsed']
                    : (array_key_exists('collapsed', $fallbackZone) ? (bool) $fallbackZone['collapsed'] : ($zone === 'top')),
                'collapsible' => array_key_exists('collapsible', $zoneConfig)
                    ? (bool) $zoneConfig['collapsible']
                    : (array_key_exists('collapsible', $fallbackZone) ? (bool) $fallbackZone['collapsible'] : true),
                'type' => (string) ($zoneConfig['type'] ?? $fallbackZone['type'] ?? 'floating'),
            ];
        }

        return $payload;
    }


    private static function normalizeIconName(mixed $icon, string $fallbackKind = '') : string
    {
        $value = is_scalar($icon) ? trim((string) $icon) : '';
        if ($value === '' && $fallbackKind !== '') {
            $value = $fallbackKind;
        }

        if ($value === '') {
            return '';
        }

        if (str_starts_with($value, 'url:') || str_starts_with($value, 'svg:') || preg_match('#^(https?:)?//#', $value) || str_starts_with($value, '/')) {
            return $value;
        }

        $normalized = strtolower(str_replace('_', '-', preg_replace('/\s+/', '-', $value)));
        if (in_array($normalized, ['crop', 'drawingareas', 'drawing-area', 'drawing-areas'], true)) {
            return 'drawing-areas';
        }

        return $value;
    }

    private static function normalizeMountTarget(mixed $target) : string
    {
        if (! is_scalar($target)) {
            return '';
        }
        $value = trim((string) $target);
        $value = (string) preg_replace('/[\x00-\x1F\x7F]/u', '', $value);
        if ($value === 'document.body') {
            return 'body';
        }
        return function_exists('mb_substr') ? mb_substr($value, 0, 240) : substr($value, 0, 240);
    }

    private static function readItemOrder(array $item, int $fallbackIndex) : int
    {
        foreach (['order', 'index', 'position'] as $key) {
            if (array_key_exists($key, $item) && is_numeric($item[$key])) {
                return max(0, (int) $item[$key]);
            }
        }

        return $fallbackIndex;
    }

    private static function isListArray(array $value) : bool
    {
        if ($value === []) {
            return true;
        }

        return array_keys($value) === range(0, count($value) - 1);
    }

    private static function looksLikeRuntimeLayout(array $layout) : bool
    {
        return isset($layout['blocksByZone']) && is_array($layout['blocksByZone']);
    }

    private static function looksLikeConfigLayout(array $layout) : bool
    {
        foreach (['left', 'right', 'top', 'bottom'] as $zone) {
            if (isset($layout[$zone]) && is_array($layout[$zone])) {
                return true;
            }
        }
        return false;
    }

    private static function mapRuntimeZoneTypeToConfigType(string $runtimeType, string $fallbackType, string $zone) : string
    {
        $runtimeType = sanitize_key($runtimeType);
        if ($runtimeType === '' || $runtimeType === 'default') {
            return $fallbackType !== '' ? $fallbackType : 'floating';
        }
        if ($runtimeType === 'panels') {
            if (strpos($fallbackType, 'panel-') === 0) {
                return $fallbackType;
            }
            return 'panel-' . $zone;
        }
        return $fallbackType !== '' ? $fallbackType : 'floating';
    }
}
