<?php
namespace Components\Data\Live;

use Components\Data\Contracts\ConfigStoreProvider;
use Components\Support\BeezUIConfigService;

class LiveBeezUIConfigStoreProvider implements ConfigStoreProvider
{
    // Caches are kept per user-scoped key so different users do not leak state
    private static array $uiOpenMaps = [];
    private static array $columnStateMaps = [];
    private static array $dismissedMaps = [];
    private static array $listLayoutMaps = [];
    private static array $listGroupedMaps = [];
    private static array $listPerPageMaps = [];

    private const KEY_STATE       = 'MAILBEEZ_CONFIG_BEEZ_UI_DATA_STATE';
    private const KEY_COLUMN_STATE       = 'MAILBEEZ_CONFIG_BEEZ_UI_DATA_COLUMN_STATE';
    private const KEY_DISMISSED   = 'MAILBEEZ_CONFIG_BEEZ_UI_DATA_DISMISSED';
    // Per-user map of list_id => layout string (e.g., 'stack', 'grid-2', 'grid-3', 'grid-4')
    private const KEY_LIST_LAYOUT = 'MAILBEEZ_CONFIG_BEEZ_UI_LIST_LAYOUT';
    // Per-user map of list_id => boolean grouped flag for list views (true/false)
    private const KEY_LIST_GROUPED = 'MAILBEEZ_CONFIG_BEEZ_UI_LIST_GROUPED';
    // Per-user map of list_id => int per_page
    private const KEY_LIST_PER_PAGE = 'MAILBEEZ_CONFIG_BEEZ_UI_LIST_PER_PAGE';
    // Per-user JSON blob for newsletter page UI state (sidebar, smart settings, etc.)
    private const KEY_NEWSLETTER_UI = 'MAILBEEZ_CONFIG_NEWSLETTER_UI_STATE';
    // Per-user map of context => view_mode string ('journey', 'performance')
    private const KEY_VIEW_MODE = 'MAILBEEZ_CONFIG_BEEZ_UI_VIEW_MODE';
    // Per-user map of context => performance_timeframe string ('1day', '7days', ...)
    private const KEY_PERFORMANCE_TIMEFRAME = 'MAILBEEZ_CONFIG_BEEZ_UI_PERFORMANCE_TIMEFRAME';

    private static array $newsletterUiMaps = [];
    private static array $viewModeMaps = [];
    private static array $performanceTimeframeMaps = [];

    public function getAllConfigs(): array { return []; }
    public function getModuleConfig(string $module): array { return []; }
    public function setModuleConfig(string $module, array $values, bool $processOnlyGivenFields = false): void {}
    public function getAllStatuses(): array { return []; }
    public function isModuleOn(string $module): bool { return false; }
    public function setModuleStatus(string $module, bool $on): void {}
    public function listKeys(string $module): array { return []; }

    public function isConfigured(string $module): bool
    {
        return BeezUIConfigService::get('CONF_SAVED_' . $module) !== false;
    }

    public function setConfigured(string $module): void
    {
        BeezUIConfigService::set('CONF_SAVED_' . $module, (string)time());
    }

    /**
     * Remove the configured marker for a module (used when a module is uninstalled).
     */
    public function clearConfigured(string $module): void
    {
        $key = 'CONF_SAVED_' . (string)$module;
        try { BeezUIConfigService::delete($key); } catch (\Throwable $e) { /* ignore */ }
        if (function_exists('mh_cfg_override_clear')) {
            try { mh_cfg_override_clear($key); } catch (\Throwable $e) { /* ignore */ }
        }
    }

    public function getUiOpenMap(): array
    {
        $scopedKey = $this->userScopedKey(self::KEY_STATE);
        if (isset(self::$uiOpenMaps[$scopedKey])) {
            return self::$uiOpenMaps[$scopedKey];
        }
        self::$uiOpenMaps[$scopedKey] = $this->loadMap($scopedKey);
        return self::$uiOpenMaps[$scopedKey];
    }

    public function setUiOpenState(string $module, bool $open): void
    {
        $module = (string)$module;
        if ($module === '') { return; }

        $scopedKey = $this->userScopedKey(self::KEY_STATE);
        $map = $this->loadMap($scopedKey);
        $map[$module] = $open;

        $this->saveMap($scopedKey, $map);
        self::$uiOpenMaps[$scopedKey] = $map;
    }

    public function getColumnStateMap(): array
    {
        $scopedKey = $this->userScopedKey(self::KEY_COLUMN_STATE);
        if (isset(self::$columnStateMaps[$scopedKey])) {
            return self::$columnStateMaps[$scopedKey];
        }
        self::$columnStateMaps[$scopedKey] = $this->loadMap($scopedKey);
        return self::$columnStateMaps[$scopedKey];
    }

    public function setColumnState(string $key, bool $visible): void
    {
        $key = (string)$key;
        if ($key === '') { return; }

        $scopedKey = $this->userScopedKey(self::KEY_COLUMN_STATE);
        $map = $this->loadMap($scopedKey);
        $map[$key] = $visible;

        $this->saveMap($scopedKey, $map);
        self::$columnStateMaps[$scopedKey] = $map;
    }

    public function getDismissedNotifications(): array
    {
        $scopedKey = $this->userScopedKey(self::KEY_DISMISSED);
        if (isset(self::$dismissedMaps[$scopedKey])) {
            return self::$dismissedMaps[$scopedKey];
        }
        self::$dismissedMaps[$scopedKey] = $this->loadMap($scopedKey);
        return self::$dismissedMaps[$scopedKey];
    }

    public function setNotificationDismissed(string $id, bool $dismissed): void
    {
        $id = (string)$id;
        if ($id === '') { return; }

        $scopedKey = $this->userScopedKey(self::KEY_DISMISSED);
        $map = $this->loadMap($scopedKey);
        $map[$id] = $dismissed;

        $this->saveMap($scopedKey, $map);
        self::$dismissedMaps[$scopedKey] = $map;
    }

    /**
     * Read the saved layout name for a logical list identifier. Defaults to 'stack'.
     * Layout values are UI-defined but commonly one of: 'stack', 'grid-2', 'grid-3', 'grid-4'.
     */
    public function getListLayout(string $listId): string
    {
        $listId = trim((string)$listId);
        if ($listId === '') { return 'stack'; }

        $scopedKey = $this->userScopedKey(self::KEY_LIST_LAYOUT);
        if (!isset(self::$listLayoutMaps[$scopedKey])) {
            // List layout map stores string values (e.g., 'grid-3'); do not normalize to booleans
            self::$listLayoutMaps[$scopedKey] = $this->loadMap($scopedKey, false);
        }
        $map = (array) self::$listLayoutMaps[$scopedKey];
        $val = isset($map[$listId]) ? (string)$map[$listId] : '';
        return $val !== '' ? $val : 'stack';
    }

    /**
     * Persist the selected layout for the given list identifier (user-scoped).
     */
    public function setListLayout(string $listId, string $layout): void
    {
        $listId = trim((string)$listId);
        $layout = trim((string)$layout);
        if ($listId === '' || $layout === '') { return; }

        $scopedKey = $this->userScopedKey(self::KEY_LIST_LAYOUT);
        // Preserve string values in the map; do not coerce to booleans
        $map = $this->loadMap($scopedKey, false);
        $map[$listId] = $layout;
        $this->saveMap($scopedKey, $map);
        self::$listLayoutMaps[$scopedKey] = $map;
    }

    /**
     * Read the saved grouped flag for a logical list identifier. Defaults to $default (true for grouped by default).
     */
    public function getListGrouped(string $listId, bool $default = true): bool
    {
        $listId = trim((string)$listId);
        if ($listId === '') { return (bool)$default; }

        $scopedKey = $this->userScopedKey(self::KEY_LIST_GROUPED);
        if (!isset(self::$listGroupedMaps[$scopedKey])) {
            self::$listGroupedMaps[$scopedKey] = $this->loadMap($scopedKey, true);
        }
        $map = (array) self::$listGroupedMaps[$scopedKey];
        if (!array_key_exists($listId, $map)) {
            return (bool)$default;
        }
        return (bool)$map[$listId];
    }

    /**
     * Persist the grouped flag for the given list identifier (user-scoped).
     */
    public function setListGrouped(string $listId, bool $grouped): void
    {
        $listId = trim((string)$listId);
        if ($listId === '') { return; }

        $scopedKey = $this->userScopedKey(self::KEY_LIST_GROUPED);
        $map = $this->loadMap($scopedKey, true);
        $map[$listId] = (bool)$grouped;
        $this->saveMap($scopedKey, $map);
        self::$listGroupedMaps[$scopedKey] = $map;
    }

    /**
     * Read the saved per-page value for a logical list identifier. Defaults to $default.
     */
    public function getListPerPage(string $listId, int $default = 25): int
    {
        $listId = trim((string)$listId);
        if ($listId === '') { return (int)$default; }

        $scopedKey = $this->userScopedKey(self::KEY_LIST_PER_PAGE);
        if (!isset(self::$listPerPageMaps[$scopedKey])) {
            self::$listPerPageMaps[$scopedKey] = $this->loadMap($scopedKey, false);
        }
        $map = (array) self::$listPerPageMaps[$scopedKey];
        if (!array_key_exists($listId, $map)) {
            return (int)$default;
        }
        return (int)$map[$listId];
    }

    /**
     * Persist the per-page value for the given list identifier (user-scoped).
     */
    public function setListPerPage(string $listId, int $perPage): void
    {
        $listId = trim((string)$listId);
        if ($listId === '') { return; }

        $scopedKey = $this->userScopedKey(self::KEY_LIST_PER_PAGE);
        $map = $this->loadMap($scopedKey, false);
        $map[$listId] = (int)$perPage;
        $this->saveMap($scopedKey, $map);
        self::$listPerPageMaps[$scopedKey] = $map;
    }

    /**
     * Read the full newsletter UI state blob for the current user.
     * Returns an associative array with keys such as:
     *   - sidebar_width (int)
     *   - sidebar_collapsed (bool)
     *   - selected_slug (string)
     *   - smart_layout ('cards'|'list')
     *   - smart_hidden (string[])
     *   - sections_open (object: section_key => bool)
     */
    public function getNewsletterUiState(string $pageMode = ''): array
    {
        if ($pageMode === '') {
            $pageMode = \Components\Newsletter\PageConfig::fromRequest()->getPageMode();
        }
        $baseKey = self::KEY_NEWSLETTER_UI . '_' . strtoupper($pageMode);
        $scopedKey = $this->userScopedKey($baseKey);
        if (isset(self::$newsletterUiMaps[$scopedKey])) {
            return self::$newsletterUiMaps[$scopedKey];
        }
        $map = $this->loadMap($scopedKey, false);
        self::$newsletterUiMaps[$scopedKey] = $map;
        return $map;
    }

    /**
     * Merge and persist newsletter UI state fields for the current user.
     * Only the keys present in $patch are updated; existing keys are preserved.
     */
    public function setNewsletterUiState(array $patch, string $pageMode = ''): void
    {
        if (empty($patch)) { return; }
        if ($pageMode === '') {
            $pageMode = \Components\Newsletter\PageConfig::fromRequest()->getPageMode();
        }
        $baseKey = self::KEY_NEWSLETTER_UI . '_' . strtoupper($pageMode);
        $scopedKey = $this->userScopedKey($baseKey);
        $map = $this->loadMap($scopedKey, false);
        foreach ($patch as $k => $v) {
            $map[(string)$k] = $v;
        }
        $this->saveMap($scopedKey, $map);
        self::$newsletterUiMaps[$scopedKey] = $map;
    }

    /**
     * Read the saved view mode for a given context. Defaults to $default.
     */
    public function getViewMode(string $context, string $default = 'journey'): string
    {
        $context = trim((string)$context);
        if ($context === '') { return $default; }

        $scopedKey = $this->userScopedKey(self::KEY_VIEW_MODE);
        if (!isset(self::$viewModeMaps[$scopedKey])) {
            self::$viewModeMaps[$scopedKey] = $this->loadMap($scopedKey, false);
        }
        $map = (array) self::$viewModeMaps[$scopedKey];
        $val = isset($map[$context]) ? (string)$map[$context] : '';
        return $val !== '' ? $val : $default;
    }

    /**
     * Persist the selected view mode for the given context (user-scoped).
     */
    public function setViewMode(string $context, string $mode): void
    {
        $context = trim((string)$context);
        $mode = trim((string)$mode);
        if ($context === '' || $mode === '') { return; }

        $scopedKey = $this->userScopedKey(self::KEY_VIEW_MODE);
        $map = $this->loadMap($scopedKey, false);
        $map[$context] = $mode;
        $this->saveMap($scopedKey, $map);
        self::$viewModeMaps[$scopedKey] = $map;
    }

    /**
     * Read the saved performance timeframe for a logical view identifier.
     */
    public function getPerformanceTimeframe(string $context, string $default = '30days'): string
    {
        $context = trim((string)$context);
        if ($context === '') { return $default; }

        $scopedKey = $this->userScopedKey(self::KEY_PERFORMANCE_TIMEFRAME);
        if (!isset(self::$performanceTimeframeMaps[$scopedKey])) {
            self::$performanceTimeframeMaps[$scopedKey] = $this->loadMap($scopedKey, false);
        }
        $map = (array) self::$performanceTimeframeMaps[$scopedKey];
        $val = isset($map[$context]) ? (string)$map[$context] : '';
        return $val !== '' ? $val : $default;
    }

    /**
     * Persist the selected performance timeframe for the given view identifier (user-scoped).
     */
    public function setPerformanceTimeframe(string $context, string $timeframe): void
    {
        $context = trim((string)$context);
        $timeframe = trim((string)$timeframe);
        if ($context === '' || $timeframe === '') { return; }

        $scopedKey = $this->userScopedKey(self::KEY_PERFORMANCE_TIMEFRAME);
        $map = $this->loadMap($scopedKey, false);
        $map[$context] = $timeframe;
        $this->saveMap($scopedKey, $map);
        self::$performanceTimeframeMaps[$scopedKey] = $map;
    }

    public function getCalendarFeedToken(): string
    {
        $scopedKey = $this->userScopedKey('BEEZUI_CALENDAR_FEED_TOKEN');
        $token = BeezUIConfigService::get($scopedKey);
        if ($token === false || $token === '') {
            $token = $this->rotateCalendarFeedToken();
        }
        return (string)$token;
    }

    public function rotateCalendarFeedToken(): string
    {
        $scopedKey = $this->userScopedKey('BEEZUI_CALENDAR_FEED_TOKEN');
        try {
            $token = bin2hex(random_bytes(16));
        } catch (\Throwable $e) {
            $token = md5(uniqid((string)mt_rand(), true));
        }
        BeezUIConfigService::set($scopedKey, $token);
        return $token;
    }

    /**
     * Load a map from a dedicated config key.
     * @param string $key
     * @param bool $normalizeBooleans When true, coerces truthy values to boolean true/false (for open/dismissed maps).
     *                                Must be false for string-valued maps (e.g., list layout).
     */
    private function loadMap(string $key, bool $normalizeBooleans = true): array
    {
        $raw = BeezUIConfigService::get($key);

        // Try legacy mh_cfg for the dedicated key
        if ($raw === false && function_exists('mh_cfg')) {
            $raw = (string)mh_cfg($key, '');
            if ($raw === '') {
                $raw = false;
            }
        }

        // If the dedicated key has data, decode and return
        if ($raw !== false && $raw !== "") {
            $decoded = json_decode($raw, true);
            if (is_array($decoded)) {
                return $normalizeBooleans ? $this->normalizeBooleans($decoded) : $decoded;
            }
        }

        return [];
    }

    private function saveMap(string $key, array $map): void
    {
        $json = json_encode($map, JSON_UNESCAPED_SLASHES);
        if ($json === false) { return; }

        BeezUIConfigService::set($key, $json);
        if (function_exists('mh_cfg_override_set')) {
            try { mh_cfg_override_set($key, $json); } catch (\Throwable $e) { /* ignore */ }
        }
    }

    private function normalizeBooleans(array $map): array
    {
        foreach ($map as &$v) {
            $v = ($v === true || $v === 1 || $v === '1' || $v === 'on' || $v === 'true');
        }
        return $map;
    }

    /**
     * Build a user-scoped configuration key by appending a stable user id.
     * Default source is mh_session('customer_id'). Falls back to '0' when unavailable.
     */
    private function userScopedKey(string $baseKey): string
    {
        $uid = $this->getCurrentUserId();
        return $baseKey . '_U_' . $uid;
    }

    /**
     * Resolve current user id for scoping UI state.
     */
    private function getCurrentUserId(): string
    {
        $id = null;
        if (function_exists('mh_session')) {
            try {
                $id = mh_session('customer_id');
            } catch (\Throwable $e) {
                $id = null;
            }
        }
        if ($id === null || $id === false || $id === '') {
            $id = '0';
        }
        return (string)$id;
    }
}
