<?php
declare(strict_types=1);

namespace Help4BuilderSuite\Modules\Calendar;

if (! defined('ABSPATH')) {
    exit;
}

final class Workspace
{
    public const OPTION = 'h4bs_calendar_workspace';
    public const SCHEMA_VERSION = '1.1';

    /** @return array<string, mixed> */
    public static function defaults(): array
    {
        return [
            'schema_version' => self::SCHEMA_VERSION,
            'revision' => 1,
            'updated_at' => '',
            'sources' => [],
            'source_events' => [],
            'local_events' => [],
            'overrides' => [],
            'extensions' => [],
        ];
    }

    /** @return array<string, mixed> */
    public static function load(): array
    {
        $saved = get_option(self::OPTION, []);
        return self::sanitize(is_array($saved) ? $saved : []);
    }

    /** @param array<string, mixed> $raw @return array<string, mixed> */
    public static function sanitize(array $raw): array
    {
        $clean = self::defaults();
        $clean['revision'] = max(1, (int) ($raw['revision'] ?? 1));
        $clean['updated_at'] = sanitize_text_field((string) ($raw['updated_at'] ?? ''));
        foreach ((array) ($raw['sources'] ?? []) as $source) {
            if (! is_array($source)) continue;
            $id = sanitize_key((string) ($source['id'] ?? ''));
            if ($id === '') continue;
            $status = sanitize_key((string) ($source['status'] ?? 'not_connected'));
            if (! in_array($status, ['not_connected', 'connected', 'syncing', 'error'], true)) $status = 'not_connected';
            $clean['sources'][] = [
                'id' => $id,
                'name' => sanitize_text_field((string) ($source['name'] ?? 'Calendar source')),
                'provider' => in_array(($source['provider'] ?? ''), ['google', 'feed', 'manual'], true) ? (string) $source['provider'] : 'google',
                'calendar_id' => sanitize_text_field((string) ($source['calendar_id'] ?? '')),
                'feed_url' => esc_url_raw((string) ($source['feed_url'] ?? '')),
                'enabled' => rest_sanitize_boolean($source['enabled'] ?? true),
                'status' => $status,
                'last_sync_at' => sanitize_text_field((string) ($source['last_sync_at'] ?? '')),
                'last_sync_count' => max(0, (int) ($source['last_sync_count'] ?? 0)),
                'last_error' => sanitize_text_field((string) ($source['last_error'] ?? '')),
                'etag' => sanitize_text_field((string) ($source['etag'] ?? '')),
                'extensions' => self::sanitizeOpaque($source['extensions'] ?? []),
            ];
        }
        foreach (['source_events', 'local_events'] as $bucket) {
            foreach ((array) ($raw[$bucket] ?? []) as $event) {
                $normalized = self::sanitizeEvent($event, $bucket === 'local_events' ? 'local' : 'source');
                if ($normalized) $clean[$bucket][] = $normalized;
            }
        }
        foreach ((array) ($raw['overrides'] ?? []) as $eventId => $override) {
            if (! is_array($override)) continue;
            $key = sanitize_text_field((string) $eventId);
            if ($key === '') continue;
            $clean['overrides'][$key] = [
                'hidden' => rest_sanitize_boolean($override['hidden'] ?? false),
                'title' => sanitize_text_field((string) ($override['title'] ?? '')),
                'description' => sanitize_textarea_field((string) ($override['description'] ?? '')),
                'category' => sanitize_text_field((string) ($override['category'] ?? '')),
                'badge' => sanitize_text_field((string) ($override['badge'] ?? '')),
                'event_status' => self::eventStatus((string) ($override['event_status'] ?? '')),
                'timezone' => sanitize_text_field((string) ($override['timezone'] ?? '')),
                'venue_name' => sanitize_text_field((string) ($override['venue_name'] ?? '')),
                'street_address' => sanitize_text_field((string) ($override['street_address'] ?? '')),
                'address_locality' => sanitize_text_field((string) ($override['address_locality'] ?? '')),
                'address_region' => sanitize_text_field((string) ($override['address_region'] ?? '')),
                'postal_code' => sanitize_text_field((string) ($override['postal_code'] ?? '')),
                'address_country' => strtoupper(substr(sanitize_text_field((string) ($override['address_country'] ?? '')), 0, 2)),
                'ticket_url' => esc_url_raw((string) ($override['ticket_url'] ?? '')),
                'offer_price' => self::price($override['offer_price'] ?? ''),
                'offer_currency' => strtoupper(substr(sanitize_text_field((string) ($override['offer_currency'] ?? '')), 0, 3)),
                'extensions' => self::sanitizeOpaque($override['extensions'] ?? []),
            ];
        }
        $clean['extensions'] = self::sanitizeOpaque($raw['extensions'] ?? []);
        foreach ($raw as $key => $value) {
            if (! array_key_exists((string) $key, $clean)) $clean['extensions']['opaque'][(string) $key] = self::sanitizeOpaque($value);
        }
        return $clean;
    }

    /** @param mixed $event @return array<string, mixed>|null */
    private static function sanitizeEvent($event, string $provenance): ?array
    {
        if (! is_array($event)) return null;
        $id = sanitize_text_field((string) ($event['id'] ?? ''));
        $title = sanitize_text_field((string) ($event['title'] ?? ''));
        if ($id === '' || $title === '') return null;
        $slug = sanitize_title((string) ($event['slug'] ?? ''));
        if ($slug === '') $slug = sanitize_title($title) . '-' . substr(md5($id), 0, 8);
        return [
            'id' => $id,
            'source_id' => sanitize_key((string) ($event['source_id'] ?? '')),
            'provenance' => $provenance,
            'title' => $title,
            'description' => sanitize_textarea_field((string) ($event['description'] ?? '')),
            'start' => sanitize_text_field((string) ($event['start'] ?? '')),
            'end' => sanitize_text_field((string) ($event['end'] ?? '')),
            'all_day' => rest_sanitize_boolean($event['all_day'] ?? false),
            'location' => sanitize_text_field((string) ($event['location'] ?? '')),
            'category' => sanitize_text_field((string) ($event['category'] ?? '')),
            'badge' => sanitize_text_field((string) ($event['badge'] ?? '')),
            'url' => esc_url_raw((string) ($event['url'] ?? '')),
            'slug' => $slug,
            'event_status' => self::eventStatus((string) ($event['event_status'] ?? 'scheduled')),
            'timezone' => sanitize_text_field((string) ($event['timezone'] ?? wp_timezone_string())),
            'venue_name' => sanitize_text_field((string) ($event['venue_name'] ?? ($event['location'] ?? ''))),
            'street_address' => sanitize_text_field((string) ($event['street_address'] ?? '')),
            'address_locality' => sanitize_text_field((string) ($event['address_locality'] ?? '')),
            'address_region' => sanitize_text_field((string) ($event['address_region'] ?? '')),
            'postal_code' => sanitize_text_field((string) ($event['postal_code'] ?? '')),
            'address_country' => strtoupper(substr(sanitize_text_field((string) ($event['address_country'] ?? 'US')), 0, 2)),
            'attendance_mode' => in_array(($event['attendance_mode'] ?? ''), ['offline', 'online', 'mixed'], true) ? (string) $event['attendance_mode'] : 'offline',
            'organizer_name' => sanitize_text_field((string) ($event['organizer_name'] ?? '')),
            'organizer_url' => esc_url_raw((string) ($event['organizer_url'] ?? '')),
            'ticket_url' => esc_url_raw((string) ($event['ticket_url'] ?? '')),
            'offer_price' => self::price($event['offer_price'] ?? ''),
            'offer_currency' => strtoupper(substr(sanitize_text_field((string) ($event['offer_currency'] ?? 'USD')), 0, 3)),
            'updated_at' => sanitize_text_field((string) ($event['updated_at'] ?? '')),
            'extensions' => self::sanitizeOpaque($event['extensions'] ?? []),
        ];
    }

    private static function eventStatus(string $value): string
    {
        $value = sanitize_key($value);
        return in_array($value, ['scheduled', 'cancelled', 'postponed', 'rescheduled', 'moved_online'], true) ? $value : 'scheduled';
    }

    /** @param mixed $value @return float|string */
    private static function price($value)
    {
        return $value === '' || $value === null ? '' : max(0, (float) $value);
    }

    /** @param mixed $value @return mixed */
    private static function sanitizeOpaque($value)
    {
        if (is_array($value)) {
            $clean = [];
            foreach ($value as $key => $item) $clean[sanitize_key((string) $key)] = self::sanitizeOpaque($item);
            return $clean;
        }
        if (is_bool($value) || is_int($value) || is_float($value)) return $value;
        return sanitize_textarea_field((string) $value);
    }

    /** @return array<int, array<string, mixed>> */
    public static function effectiveEvents(array $sourceIds = []): array
    {
        $data = self::load();
        $events = array_merge((array) $data['source_events'], (array) $data['local_events']);
        $effective = [];
        foreach ($events as $event) {
            if ($sourceIds && $event['provenance'] === 'source' && ! in_array($event['source_id'], $sourceIds, true)) continue;
            $override = $data['overrides'][$event['id']] ?? [];
            if (! empty($override['hidden'])) continue;
            foreach (['title', 'description', 'category', 'badge', 'event_status', 'timezone', 'venue_name', 'street_address', 'address_locality', 'address_region', 'postal_code', 'address_country', 'ticket_url', 'offer_price', 'offer_currency'] as $key) {
                if (array_key_exists($key, $override) && $override[$key] !== '') $event[$key] = $override[$key];
            }
            $effective[] = $event;
        }
        usort($effective, static fn(array $a, array $b): int => strcmp((string) $a['start'], (string) $b['start']));
        return $effective;
    }

    /** @param array<string, mixed> $settings */
    public static function render(array $settings): string
    {
        $sourceIds = array_values(array_filter(array_map('sanitize_key', preg_split('/\s*,\s*/', (string) ($settings['source_ids'] ?? '')) ?: [])));
        $events = self::effectiveEvents($sourceIds);
        $presentation = in_array(($settings['presentation'] ?? ''), ['calendar', 'list'], true) ? (string) $settings['presentation'] : 'calendar';
        $search = ! array_key_exists('show_search', $settings) || rest_sanitize_boolean($settings['show_search']);
        $filters = ! array_key_exists('show_filters', $settings) || rest_sanitize_boolean($settings['show_filters']);
        $empty = sanitize_text_field((string) ($settings['empty_text'] ?? __('No events match your filters.', 'help4-builder-suite')));
        $columns = max(1, min(6, (int) ($settings['columns'] ?? 3)));
        $tablet = max(1, min(4, (int) ($settings['columns_tablet'] ?? 2)));
        $mobile = max(1, min(2, (int) ($settings['columns_mobile'] ?? 1)));
        $categories = [];
        foreach ($events as $event) if ($event['category'] !== '') $categories[] = $event['category'];
        $categories = array_values(array_unique($categories));
        $html = '<section class="h4bs-calendar-view is-' . esc_attr($presentation) . '" data-h4bs-calendar-view style="--h4bs-calendar-columns:' . $columns . ';--h4bs-calendar-columns-tablet:' . $tablet . ';--h4bs-calendar-columns-mobile:' . $mobile . '">';
        if ($search || $filters) {
            $html .= '<div class="h4bs-calendar-tools">';
            if ($search) $html .= '<label><span>' . esc_html__('Search events', 'help4-builder-suite') . '</span><input type="search" data-h4bs-calendar-search></label>';
            if ($filters) {
                $html .= '<label><span>' . esc_html__('Category', 'help4-builder-suite') . '</span><select data-h4bs-calendar-filter><option value="">' . esc_html__('All categories', 'help4-builder-suite') . '</option>';
                foreach ($categories as $category) $html .= '<option value="' . esc_attr(strtolower($category)) . '">' . esc_html($category) . '</option>';
                $html .= '</select></label>';
            }
            $html .= '</div>';
        }
        $html .= '<div class="h4bs-calendar-events" data-h4bs-calendar-events>';
        foreach ($events as $event) {
            $searchText = strtolower(implode(' ', [$event['title'], $event['description'], $event['location'], $event['category']]));
            $html .= '<article class="h4bs-calendar-event" data-h4bs-calendar-event data-search="' . esc_attr($searchText) . '" data-category="' . esc_attr(strtolower($event['category'])) . '">'
                . '<div class="h4bs-calendar-date"><time datetime="' . esc_attr($event['start']) . '">' . esc_html(self::formatDate($event['start'])) . '</time></div>'
                . '<div><span class="h4bs-calendar-provenance">' . esc_html($event['provenance'] === 'local' ? __('Local', 'help4-builder-suite') : __('Synced', 'help4-builder-suite')) . '</span>'
                . ($event['badge'] !== '' ? '<span class="h4bs-calendar-badge">' . esc_html($event['badge']) . '</span>' : '')
                . '<h3><a href="' . esc_url(self::eventUrl($event)) . '">' . esc_html($event['title']) . '</a></h3>'
                . ($event['description'] !== '' ? '<p>' . nl2br(esc_html($event['description'])) . '</p>' : '')
                . ($event['location'] !== '' ? '<p class="h4bs-calendar-location">' . esc_html($event['location']) . '</p>' : '') . '</div></article>';
        }
        $html .= '</div><p data-h4bs-calendar-empty hidden>' . esc_html($empty) . '</p><p class="screen-reader-text" aria-live="polite" data-h4bs-calendar-status></p></section>';
        return $html;
    }

    /** @param array<string, mixed> $event */
    public static function eventUrl(array $event): string
    {
        return home_url('/events/' . rawurlencode((string) ($event['slug'] ?? '')) . '/');
    }

    /** @return array<string, mixed>|null */
    public static function eventBySlug(string $slug): ?array
    {
        $slug = sanitize_title($slug);
        foreach (self::effectiveEvents() as $event) if (hash_equals((string) $event['slug'], $slug)) return $event;
        return null;
    }

    /** @param array<string, mixed> $event @return array{eligible: bool, missing: array<int, string>} */
    public static function searchReadiness(array $event): array
    {
        $missing = [];
        if (trim((string) ($event['title'] ?? '')) === '') $missing[] = 'title';
        if (trim((string) ($event['description'] ?? '')) === '') $missing[] = 'description';
        if (! self::validDate((string) ($event['start'] ?? ''), (string) ($event['timezone'] ?? ''))) $missing[] = 'start date and timezone';
        if (! in_array((string) ($event['event_status'] ?? ''), ['scheduled', 'cancelled', 'postponed', 'rescheduled', 'moved_online'], true)) $missing[] = 'event status';
        $mode = (string) ($event['attendance_mode'] ?? 'offline');
        if (! in_array($mode, ['offline', 'online', 'mixed'], true)) $missing[] = 'attendance mode';
        if (in_array($mode, ['offline', 'mixed'], true)) {
            foreach (['venue_name' => 'venue', 'street_address' => 'street address', 'address_locality' => 'city', 'address_country' => 'country'] as $key => $label) {
                if (trim((string) ($event[$key] ?? '')) === '') $missing[] = $label;
            }
        }
        if (in_array($mode, ['online', 'mixed'], true) && esc_url_raw((string) ($event['url'] ?? $event['ticket_url'] ?? '')) === '') $missing[] = 'online event URL';
        return ['eligible' => $missing === [], 'missing' => array_values(array_unique($missing))];
    }

    private static function validDate(string $value, string $timezone): bool
    {
        if ($value === '' || $timezone === '') return false;
        try {
            new \DateTimeZone($timezone);
            new \DateTimeImmutable($value, new \DateTimeZone($timezone));
            return true;
        } catch (\Exception $exception) {
            return false;
        }
    }

    private static function formatDate(string $value): string
    {
        $timestamp = strtotime($value);
        return $timestamp ? wp_date('D, M j, Y g:i a', $timestamp) : $value;
    }
}
