<?php

declare(strict_types=1);

/*
 * This file is part of the VITICORE2 package.
 *
 * (c) CP Creation <web@cpcreation.fr>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace CpCreation\VitiCore\Seo;

use CpCreation\VitiCore\Contact\Model\Contact;

final class LocalBusinessSchemaBuilder
{
    private const DAYS = [
        'Monday',
        'Tuesday',
        'Wednesday',
        'Thursday',
        'Friday',
        'Saturday',
        'Sunday',
    ];

    /**
     * @return array<string, mixed>|null
     */
    public function build(Contact $contact, string $businessUrl, ?string $logoUrl = null): ?array
    {
        $name = $this->normalizeText($contact->getCompany());
        if (null === $name) {
            return null;
        }

        $businessUrl = rtrim($businessUrl, '/') . '/';
        $schema = [
            '@context' => 'https://schema.org',
            '@type' => 'Winery',
            '@id' => $businessUrl . '#winery',
            'name' => $name,
            'url' => $businessUrl,
            'address' => $this->withoutNullValues([
                '@type' => 'PostalAddress',
                'streetAddress' => $this->normalizeText($contact->getAddress()),
                'postalCode' => $this->normalizeText($contact->getPostal()),
                'addressLocality' => $this->normalizeText($contact->getCity()),
                'addressCountry' => $this->normalizeText($contact->getCountry()),
            ]),
        ];

        if (null !== $telephone = $this->normalizeText($contact->getPhone1())) {
            $schema['telephone'] = $telephone;
        }

        if (null !== $logoUrl = $this->normalizeText($logoUrl)) {
            $schema['logo'] = $logoUrl;
            $schema['image'] = $logoUrl;
        }

        $sameAs = array_values(array_filter([
            $this->normalizeText($contact->getFacebook()),
            $this->normalizeText($contact->getInstagram()),
            $this->normalizeText($contact->getTwitter()),
        ]));
        if ([] !== $sameAs) {
            $schema['sameAs'] = $sameAs;
        }

        $latitude = $contact->getLatitude();
        $longitude = $contact->getLongitude();
        if (is_numeric($latitude) && is_numeric($longitude)) {
            $schema['geo'] = [
                '@type' => 'GeoCoordinates',
                'latitude' => (float) $latitude,
                'longitude' => (float) $longitude,
            ];
        }

        $openingHours = $this->buildOpeningHours($contact->getOpeningHours());
        if ([] !== $openingHours) {
            $schema['openingHoursSpecification'] = $openingHours;
        }

        return $schema;
    }

    /**
     * @param list<array{dayOfWeek?: mixed, opens?: mixed, closes?: mixed}> $openingHours
     *
     * @return list<array{'@type': string, dayOfWeek: list<string>, opens: string, closes: string}>
     */
    private function buildOpeningHours(array $openingHours): array
    {
        $specifications = [];

        foreach ($openingHours as $openingHour) {
            $days = is_array($openingHour['dayOfWeek'] ?? null)
                ? array_values(array_intersect(self::DAYS, $openingHour['dayOfWeek']))
                : [];
            $opens = $this->normalizeTime($openingHour['opens'] ?? null);
            $closes = $this->normalizeTime($openingHour['closes'] ?? null);

            if ([] === $days || null === $opens || null === $closes) {
                continue;
            }

            $specifications[] = [
                '@type' => 'OpeningHoursSpecification',
                'dayOfWeek' => $days,
                'opens' => $opens,
                'closes' => $closes,
            ];
        }

        return $specifications;
    }

    private function normalizeText(?string $value): ?string
    {
        if (null === $value || '' === $value = mb_trim($value)) {
            return null;
        }

        return $value;
    }

    private function normalizeTime(mixed $value): ?string
    {
        if (!is_string($value) || 1 !== preg_match('/^(?:[01]\d|2[0-3]):[0-5]\d$/', $value)) {
            return null;
        }

        return $value;
    }

    /**
     * @param array<string, mixed> $values
     *
     * @return array<string, mixed>
     */
    private function withoutNullValues(array $values): array
    {
        return array_filter($values, static fn(mixed $value): bool => null !== $value);
    }
}
