<?php

declare(strict_types=1);

namespace CliqThemes\StoreLocator\Support;

defined('ABSPATH') || exit;

use CliqThemes\StoreLocator\Repository\StoreRepository;

/**
 * Builds schema.org LocalBusiness JSON-LD for stores, improving local SEO and
 * rich results — something the storefront and store-detail pages can emit.
 */
final class SeoSchemaService
{
    public function __construct(private readonly StoreRepository $stores = new StoreRepository())
    {
    }

    /**
     * @return array<string, mixed>|null
     */
    public function forStore(int $storeId): ?array
    {
        $store = $this->stores->find($storeId);
        if (! $store) {
            return null;
        }

        $location = $store['locations'][0] ?? [];

        $schema = [
            '@context' => 'https://schema.org',
            '@type' => 'LocalBusiness',
            'name' => $store['name'] ?? '',
        ];

        if (! empty($store['description'])) {
            $schema['description'] = wp_strip_all_tags((string) $store['description']);
        }
        if (! empty($store['phone'])) {
            $schema['telephone'] = $store['phone'];
        }
        if (! empty($store['email'])) {
            $schema['email'] = $store['email'];
        }
        if (! empty($store['website'])) {
            $schema['url'] = $store['website'];
        }

        if ($location !== []) {
            $schema['address'] = array_filter([
                '@type' => 'PostalAddress',
                'streetAddress' => $location['address_line1'] ?? '',
                'addressLocality' => $location['city'] ?? '',
                'addressRegion' => $location['state'] ?? '',
                'postalCode' => $location['postal_code'] ?? '',
                'addressCountry' => $location['country'] ?? '',
            ]);

            if (! empty($location['latitude'])) {
                $schema['geo'] = [
                    '@type' => 'GeoCoordinates',
                    'latitude' => (float) $location['latitude'],
                    'longitude' => (float) ($location['longitude'] ?? 0),
                ];
            }

            $savedHours = StorePayloadFormatter::decodeOpeningHours($store['meta']['opening_hours'] ?? null);
            $hours = $this->openingHours($savedHours !== [] ? $savedHours : ($location['hours'] ?? []));
            if ($hours !== []) {
                $schema['openingHoursSpecification'] = $hours;
            }
        }

        /** @var array<string, mixed> $schema */
        $schema = apply_filters('cliq_sl_store_schema', $schema, $store);

        return $schema;
    }

    /**
     * Render the JSON-LD <script> tag for a store.
     */
    public function scriptTag(int $storeId): string
    {
        $schema = $this->forStore($storeId);
        if ($schema === null) {
            return '';
        }

        // Inline by necessity, not by preference: JSON-LD is a data island that
        // crawlers read out of the document itself, so it cannot be enqueued as
        // a file. It carries no executable JavaScript.
        //
        // JSON_HEX_TAG escapes < and > so a store field containing </script>
        // cannot break out of the JSON-LD block. Slashes are left escaped
        // (no JSON_UNESCAPED_SLASHES) for the same reason.
        return '<script type="application/ld+json">'
            . wp_json_encode($schema, JSON_UNESCAPED_UNICODE | JSON_HEX_TAG)
            . '</script>';
    }

    /**
     * Convert a HoursField value object into schema.org OpeningHoursSpecification.
     *
     * @param array<string, mixed> $hours
     *
     * @return array<int, array<string, mixed>>
     */
    private function openingHours(array $hours): array
    {
        $map = [
            'mon' => 'Monday', 'tue' => 'Tuesday', 'wed' => 'Wednesday', 'thu' => 'Thursday',
            'fri' => 'Friday', 'sat' => 'Saturday', 'sun' => 'Sunday',
        ];

        $spec = [];
        foreach ($map as $key => $day) {
            $entry = $hours[$key] ?? null;
            if (! is_array($entry) || ! empty($entry['closed'])) {
                continue;
            }
            foreach (($entry['slots'] ?? []) as $slot) {
                if (empty($slot['from']) || empty($slot['to'])) {
                    continue;
                }
                $spec[] = [
                    '@type' => 'OpeningHoursSpecification',
                    'dayOfWeek' => 'https://schema.org/' . $day,
                    'opens' => $slot['from'],
                    'closes' => $slot['to'],
                ];
            }
        }

        return $spec;
    }
}
