<?php

namespace WPSN\Modules\LocalSEO;

defined('ABSPATH') || exit;

/**
 * Best-effort parser for business opening hours.
 *
 * Input format (examples):
 *  - Mo-Fr 09:00-18:00
 *  - Sa 10:00-14:00
 *  - Mon,Wed,Fri 09:00-17:00
 *  - Mon-Fri 9:00-18:00 (RU day abbreviations are also supported)
 *  - 24/7
 *
 * Output: schema.org OpeningHoursSpecification[] (array of associative arrays).
 */
final class OpeningHoursParser
{
    /**
     * @return array<int, array<string, mixed>>
     */
    public function parse(string $raw): array
    {
        $raw = trim($raw);
        if ($raw === '') {
            return [];
        }

        $lines = preg_split('/\r\n|\r|\n/', $raw);
        $lines = is_array($lines) ? array_values(array_filter(array_map('trim', $lines), static fn ($s) => $s !== '')) : [];
        if (empty($lines)) {
            return [];
        }

        $out = [];
        foreach ($lines as $line) {
            $specs = $this->parseLine((string) $line);
            if (!empty($specs)) {
                foreach ($specs as $s) {
                    $out[] = $s;
                }
            }
        }

        return $out;
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    private function parseLine(string $line): array
    {
        $line = trim($line);
        if ($line === '') {
            return [];
        }

        // Normalize dashes.
        $line = str_replace(["–", "—", "−"], '-', $line);

        $lower = $this->lower($line);

        // 24/7 shortcuts.
        if (strpos($lower, '24/7') !== false || strpos($lower, '24x7') !== false || preg_match('/\b24\s*h(ours)?\b/u', $lower)) {
            return $this->specForAllDays('00:00', '23:59');
        }

        // Find first digit (start of the time part).
        $pos = null;
        if (preg_match('/\d/u', $line, $m, PREG_OFFSET_CAPTURE)) {
            $pos = (int) $m[0][1];
        }

        if ($pos === null || $pos < 1) {
            // Closed days or unknown format.
            return [];
        }

        $dayPart = trim(substr($line, 0, $pos));
        $timePart = trim(substr($line, $pos));

        if ($dayPart === '' || $timePart === '') {
            return [];
        }

        $days = $this->parseDays($dayPart);
        if (empty($days)) {
            return [];
        }

        $ranges = $this->parseTimeRanges($timePart);
        if (empty($ranges)) {
            return [];
        }

        $out = [];
        foreach ($days as $dayIdx) {
            $dayOfWeek = $this->schemaDayOfWeek($dayIdx);
            foreach ($ranges as [$opens, $closes]) {
                $out[] = [
                    '@type' => 'OpeningHoursSpecification',
                    'dayOfWeek' => $dayOfWeek,
                    'opens' => $opens,
                    'closes' => $closes,
                ];
            }
        }

        return $out;
    }

    /**
     * @return array<int,int> list of day indexes (0=Mon..6=Sun)
     */
    private function parseDays(string $dayPart): array
    {
        $s = trim($dayPart);
        if ($s === '') {
            return [];
        }
        $s = str_replace(["–", "—", "−"], '-', $s);
        $s = str_replace(['.', ' '], ['', ''], $s);
        $s = $this->lower($s);

        // Common shortcuts.
        if (in_array($s, ['daily', 'everyday', 'every-day', 'ежедневно', 'каждыйдень'], true)) {
            return [0, 1, 2, 3, 4, 5, 6];
        }
        if (in_array($s, ['weekdays', 'будни'], true)) {
            return [0, 1, 2, 3, 4];
        }
        if (in_array($s, ['weekend', 'выходные'], true)) {
            return [5, 6];
        }

        // Split by commas/semicolons/slashes.
        $s = str_replace([';', '/', '|'], ',', $s);
        $parts = array_values(array_filter(array_map('trim', explode(',', $s)), static fn ($x) => $x !== ''));

        $days = [];
        foreach ($parts as $p) {
            if ($p === '') {
                continue;
            }
            if (strpos($p, '-') !== false) {
                [$a, $b] = array_map('trim', explode('-', $p, 2));
                $ai = $this->dayTokenToIndex($a);
                $bi = $this->dayTokenToIndex($b);
                if ($ai === null || $bi === null) {
                    continue;
                }
                if ($ai <= $bi) {
                    for ($i = $ai; $i <= $bi; $i++) {
                        $days[] = $i;
                    }
                } else {
                    // Wrap-around, e.g. Fri-Mon.
                    for ($i = $ai; $i <= 6; $i++) {
                        $days[] = $i;
                    }
                    for ($i = 0; $i <= $bi; $i++) {
                        $days[] = $i;
                    }
                }
                continue;
            }

            $idx = $this->dayTokenToIndex($p);
            if ($idx !== null) {
                $days[] = $idx;
            }
        }

        $days = array_values(array_unique(array_filter($days, static fn ($x) => is_int($x) && $x >= 0 && $x <= 6)));
        sort($days);
        return $days;
    }

    private function dayTokenToIndex(string $token): ?int
    {
        $t = $this->lower(trim($token));
        $t = str_replace(['.', ' '], ['', ''], $t);
        if ($t === '') {
            return null;
        }

        $map = [
            // EN
            'mo' => 0, 'mon' => 0, 'monday' => 0,
            'tu' => 1, 'tue' => 1, 'tues' => 1, 'tuesday' => 1,
            'we' => 2, 'wed' => 2, 'wednesday' => 2,
            'th' => 3, 'thu' => 3, 'thur' => 3, 'thurs' => 3, 'thursday' => 3,
            'fr' => 4, 'fri' => 4, 'friday' => 4,
            'sa' => 5, 'sat' => 5, 'saturday' => 5,
            'su' => 6, 'sun' => 6, 'sunday' => 6,

            // RU (short)
            'пн' => 0, 'пон' => 0, 'понедельник' => 0,
            'вт' => 1, 'вторник' => 1,
            'ср' => 2, 'среда' => 2,
            'чт' => 3, 'чет' => 3, 'четверг' => 3,
            'пт' => 4, 'пятница' => 4,
            'сб' => 5, 'суббота' => 5,
            'вс' => 6, 'воскресенье' => 6,
        ];

        if (array_key_exists($t, $map)) {
            return (int) $map[$t];
        }

        // Fuzzy: handle things like "понед" or "сред".
        foreach ($map as $k => $v) {
            if (strlen($k) >= 3 && strpos($t, $k) === 0) {
                return (int) $v;
            }
        }

        return null;
    }

    /**
     * @return array<int, array{0:string,1:string}>
     */
    private function parseTimeRanges(string $timePart): array
    {
        $s = trim($timePart);
        if ($s === '') {
            return [];
        }

        $s = str_replace(["–", "—", "−"], '-', $s);
        $s = $this->lower($s);

        // Closed.
        if (strpos($s, 'closed') !== false || strpos($s, 'закрыто') !== false || strpos($s, 'выходн') !== false) {
            return [];
        }

        // Normalize separators.
        $s = str_replace([';', '/', '|'], ',', $s);
        $chunks = array_values(array_filter(array_map('trim', explode(',', $s)), static fn ($x) => $x !== ''));
        if (empty($chunks)) {
            $chunks = [$s];
        }

        $out = [];
        foreach ($chunks as $chunk) {
            $chunk = trim($chunk);
            if ($chunk === '') {
                continue;
            }

            // Allow 09.00-18.00.
            $chunk = str_replace('.', ':', $chunk);

            if (!preg_match('/^(\d{1,2})(?::(\d{2}))?\s*-\s*(\d{1,2})(?::(\d{2}))?$/u', $chunk, $m)) {
                continue;
            }

            $h1 = (int) $m[1];
            $m1 = isset($m[2]) && $m[2] !== '' ? (int) $m[2] : 0;
            $h2 = (int) $m[3];
            $m2 = isset($m[4]) && $m[4] !== '' ? (int) $m[4] : 0;

            if ($h1 < 0 || $h1 > 23 || $h2 < 0 || $h2 > 23 || $m1 < 0 || $m1 > 59 || $m2 < 0 || $m2 > 59) {
                continue;
            }

            $opens = sprintf('%02d:%02d', $h1, $m1);
            $closes = sprintf('%02d:%02d', $h2, $m2);
            $out[] = [$opens, $closes];
        }

        return $out;
    }

    private function schemaDayOfWeek(int $idx): string
    {
        $names = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
        $name = isset($names[$idx]) ? (string) $names[$idx] : 'Monday';
        return 'https://schema.org/' . $name;
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    private function specForAllDays(string $opens, string $closes): array
    {
        $out = [];
        for ($i = 0; $i <= 6; $i++) {
            $out[] = [
                '@type' => 'OpeningHoursSpecification',
                'dayOfWeek' => $this->schemaDayOfWeek($i),
                'opens' => $opens,
                'closes' => $closes,
            ];
        }
        return $out;
    }

    private function lower(string $s): string
    {
        if (function_exists('mb_strtolower')) {
            return (string) mb_strtolower($s);
        }
        return strtolower($s);
    }
}
