<?php
// components/SortStore.php
// Prefer legacy *_SORT_ORDER configuration for ordering; keep JSON as fallback

namespace Components;

class SortStore
{
    private const STORE_FILE = 'var/sort_store.json';
    private const CAMPAIGN_SORT_KEY = 'campaign_sort_order';

    /** Resolve absolute path to store file and ensure directory exists */
    private static function storePath(): string
    {
        $root = defined('BEEZUI_ROOT') ? BEEZUI_ROOT : dirname(__DIR__);
        $path = $root . DIRECTORY_SEPARATOR . self::STORE_FILE;
        $dir = dirname($path);
        if (!is_dir($dir)) {
            @mkdir($dir, 0775, true);
        }
        return $path;
    }

    /** Read entire store (associative array); return [] if missing or invalid */
    private static function readStore(): array
    {
        $path = self::storePath();
        if (!is_file($path)) {
            return [];
        }
        $json = @file_get_contents($path);
        if ($json === false || $json === '') {
            return [];
        }
        $data = json_decode($json, true);
        return is_array($data) ? $data : [];
    }

    /** Write entire store atomically; ignore errors silently */
    private static function writeStore(array $store): void
    {
        $path = self::storePath();
        $json = json_encode($store, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
        if ($json === false) {
            return; // nothing we can do
        }
        @file_put_contents($path, $json, LOCK_EX);
    }

    /**
     * Try to resolve the *_SORT_ORDER configuration key name for a module.
     */
    private static function resolveSortOrderKey(string $moduleKey): ?string
    {
        try {
            $all = ModuleLoader::all();
            if (!isset($all[$moduleKey])) { return null; }
            $meta = $all[$moduleKey];
            // 1) Look for any config key ending with _SORT_ORDER
            if (isset($meta['config_keys']) && is_array($meta['config_keys'])) {
                foreach ($meta['config_keys'] as $k) {
                    if (is_string($k) && preg_match('/_SORT_ORDER$/', $k)) {
                        return $k;
                    }
                }
            }
            // 2) Derive from status_key by replacing _STATUS
            if (!empty($meta['status_key']) && is_string($meta['status_key'])) {
                $candidate = preg_replace('/_STATUS$/', '_SORT_ORDER', (string)$meta['status_key']);
                if (is_string($candidate) && $candidate !== $meta['status_key']) {
                    return $candidate;
                }
            }
        } catch (\Throwable $e) {
            // ignore and try fallback
        }
        // 3) Last resort: MAILBEEZ_ . strtoupper(key) . _SORT_ORDER
        $fallback = 'MAILBEEZ_' . strtoupper($moduleKey) . '_SORT_ORDER';
        return $fallback;
    }

    /** Read sort order for module as integer or null if not set/invalid */
    private static function readSortOrder(string $moduleKey): ?int
    {
        $cfgKey = self::resolveSortOrderKey($moduleKey);
        if (is_string($cfgKey) && $cfgKey !== '' && function_exists('mh_cfg')) {
            try {
                $val = mh_cfg($cfgKey, null);
                if ($val === null || $val === '') { return null; }
                if (is_numeric($val)) { return (int)$val; }
            } catch (\Throwable $e) { /* ignore */ }
        }
        return null;
    }

    /** Persist sort order to legacy config storage; returns true if write path attempted */
    private static function writeSortOrder(string $moduleKey, int $value): bool
    {
        $cfgKey = self::resolveSortOrderKey($moduleKey);
        if (!is_string($cfgKey) || $cfgKey === '') { return false; }
        $ok = false;
        if (function_exists('mh_update_config_value')) {
            try {
                mh_update_config_value([
                    'configuration_key' => $cfgKey,
                    'configuration_value' => (string)$value,
                ]);
                $ok = true;
                // Make sure current request sees updated value if override helper exists
                if (function_exists('mh_cfg_override_set')) {
                    try { mh_cfg_override_set($cfgKey, (string)$value); } catch (\Throwable $e2) { /* ignore */ }
                } elseif (function_exists('mh_cfg_refresh')) {
                    try { mh_cfg_refresh($cfgKey); } catch (\Throwable $e3) { /* ignore */ }
                }
            } catch (\Throwable $e) {
                // ignore
            }
        }
        return $ok;
    }

    /**
     * Build a map from module key => sort position read from legacy config.
     * Only includes modules with a numeric value.
     *
     * @param array<int,string>|null $moduleKeys
     * @return array<string,int>
     */
    private static function getConfigOrderMap(?array $moduleKeys = null): array
    {
        $map = [];
        try {
            $keys = [];
            if (is_array($moduleKeys) && !empty($moduleKeys)) {
                foreach ($moduleKeys as $candidate) {
                    $candidate = trim((string)$candidate);
                    if ($candidate !== '') {
                        $keys[$candidate] = true;
                    }
                }
                $keys = array_keys($keys);
            } else {
                $byGroup = ModuleLoader::campaignsByGroup();
                foreach ($byGroup as $mods) {
                    foreach ($mods as $m) {
                        $key = isset($m['key']) ? (string)$m['key'] : '';
                        if ($key !== '') {
                            $keys[$key] = true;
                        }
                    }
                }
                $keys = array_keys($keys);
            }

            foreach ($keys as $key) {
                $pos = self::readSortOrder($key);
                if ($pos !== null) {
                    $map[$key] = $pos;
                }
            }
        } catch (\Throwable $e) {
            // ignore
        }
        return $map;
    }

    /**
     * @return array<string,int>
     */
    private static function getJsonOrderMap(): array
    {
        $store = self::readStore();
        $legacy = $store[self::CAMPAIGN_SORT_KEY] ?? [];
        if (!is_array($legacy)) {
            return [];
        }
        $out = [];
        foreach ($legacy as $k => $pos) {
            if (!is_string($k)) {
                continue;
            }
            $out[$k] = (int)$pos;
        }
        return $out;
    }

    /**
     * Returns the stored order map for campaign modules: key => position (0 = top)
     * Prefers legacy *_SORT_ORDER config values; falls back to JSON store if none found.
     * @return array<string,int>
     */
    public static function getCampaignOrderMap(): array
    {
        $map = self::getConfigOrderMap();
        if (!empty($map)) { return $map; }
        // Fallback to previous JSON file if config not populated yet
        return self::getJsonOrderMap();
    }

    /**
     * Persist order by list of module keys. First item gets 0, then 10, 20, ...
     * Unknown keys are skipped.
     *
     * @param array<int,string> $orderedKeys
     */
    public static function setCampaignOrderFromList(array $orderedKeys): void
    {
        $map = [];
        $i = 0;
        $persistedAny = false;
        foreach ($orderedKeys as $key) {
            $key = (string)$key;
            if ($key === '') continue;
            $value = $i * 10; // steps of 10, 0 is first
            $map[$key] = $value;
            // Try to persist into legacy config
            $ok = self::writeSortOrder($key, $value);
            if ($ok) { $persistedAny = true; }
            $i++;
        }
        // Keep writing to JSON as a safety net or when legacy write unavailable
        $store = self::readStore();
        $store[self::CAMPAIGN_SORT_KEY] = $map;
        self::writeStore($store);
    }

    /**
     * Apply ordering to a grouped modules list. Items missing from the map/values are placed after those with a position,
     * keeping their original relative order.
     *
     * @param array<string, array<int, array<string,mixed>>> $modulesByGroup
     * @return array<string, array<int, array<string,mixed>>> Sorted per group
     */
    public static function applyOrderToGrouped(array $modulesByGroup): array
    {
        // Prefer config values per module; fallback to JSON map if no config present
        $moduleKeys = [];
        foreach ($modulesByGroup as $mods) {
            foreach ($mods as $m) {
                $key = isset($m['key']) ? (string)$m['key'] : '';
                if ($key !== '') {
                    $moduleKeys[$key] = true;
                }
            }
        }
        $moduleKeys = array_keys($moduleKeys);

        $configMap = self::getConfigOrderMap($moduleKeys);
        $jsonMap = empty($configMap) ? self::getJsonOrderMap() : [];

        foreach ($modulesByGroup as $group => $mods) {
            // decorate-sort-undecorate to maintain stable ordering for ties
            $decorated = [];
            $seq = 0;
            foreach ($mods as $m) {
                $key = $m['key'] ?? '';
                $pos = $configMap[$key] ?? ($jsonMap[$key] ?? PHP_INT_MAX); // unknown go last
                $decorated[] = ['pos' => (int)$pos, 'seq' => $seq++, 'm' => $m];
            }
            usort($decorated, function($a, $b){
                if ($a['pos'] === $b['pos']) return $a['seq'] <=> $b['seq'];
                return $a['pos'] <=> $b['pos'];
            });
            $modulesByGroup[$group] = array_map(fn($d) => $d['m'], $decorated);
        }
        return $modulesByGroup;
    }

    /**
     * Return a flat list of all campaign modules ordered according to *_SORT_ORDER values.
     * Items missing a value come after the known ones, in original order.
     * @return array<int, array<string,mixed>>
     */
    public static function campaignListOrdered(): array
    {
        $byGroup = ModuleLoader::campaignsByGroup();
        $flat = [];
        foreach ($byGroup as $mods) {
            foreach ($mods as $m) { $flat[] = $m; }
        }
        $moduleKeys = [];
        foreach ($flat as $m) {
            $key = isset($m['key']) ? (string)$m['key'] : '';
            if ($key !== '') {
                $moduleKeys[$key] = true;
            }
        }
        $configMap = self::getConfigOrderMap(array_keys($moduleKeys));
        $jsonMap = empty($configMap) ? self::getJsonOrderMap() : [];
        if (empty($configMap) && empty($jsonMap)) { return $flat; }
        $decorated = [];
        $seq = 0;
        foreach ($flat as $m) {
            $key = $m['key'] ?? '';
            $pos = $configMap[$key] ?? ($jsonMap[$key] ?? PHP_INT_MAX);
            $decorated[] = ['pos' => (int)$pos, 'seq' => $seq++, 'm' => $m];
        }
        usort($decorated, function($a, $b){
            if ($a['pos'] === $b['pos']) return $a['seq'] <=> $b['seq'];
            return $a['pos'] <=> $b['pos'];
        });
        return array_map(fn($d) => $d['m'], $decorated);
    }
}
