<?php
declare(strict_types=1);

/**
 * 地址轉座標：OpenStreetMap Nominatim（主要）+ Photon 備援。
 * https://operations.osmfoundation.org/policies/nominatim/
 */

if (!function_exists('fate_osm_nominatim_user_agent')) {
    function fate_osm_nominatim_user_agent(): string
    {
        $origin = '';
        if (!empty($_SERVER['HTTP_HOST'])) {
            $scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
            $origin = $scheme . '://' . $_SERVER['HTTP_HOST'];
        }

        return 'FATEStoreAdmin/1.0 (' . ($origin !== '' ? $origin : 'local') . '; store geocode)';
    }
}

if (!function_exists('fate_osm_rate_limit_wait')) {
    function fate_osm_rate_limit_wait(): void
    {
        static $lastAt = 0.0;
        $now = microtime(true);
        if ($lastAt > 0 && ($now - $lastAt) < 1.05) {
            usleep((int) ((1.05 - ($now - $lastAt)) * 1000000));
        }
        $lastAt = microtime(true);
    }
}

if (!function_exists('fate_parse_manual_gps')) {
    function fate_parse_manual_gps(string $raw): ?string
    {
        $raw = trim($raw);
        if ($raw === '' || !preg_match('/^(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)$/', $raw, $m)) {
            return null;
        }
        $lat = (float) $m[1];
        $lng = (float) $m[2];
        if (!is_finite($lat) || !is_finite($lng) || abs($lat) > 90 || abs($lng) > 180) {
            return null;
        }

        return number_format($lat, 6, '.', '') . ',' . number_format($lng, 6, '.', '');
    }
}

if (!function_exists('fate_normalize_tw_address_part')) {
    function fate_normalize_tw_address_part(string $s): string
    {
        $s = trim($s);
        $s = str_replace(['　', ' '], '', $s);

        return $s;
    }
}

if (!function_exists('fate_tw_text_variants')) {
    /**
     * @return list<string>
     */
    function fate_tw_text_variants(string $s): array
    {
        $s = fate_normalize_tw_address_part($s);
        if ($s === '') {
            return [];
        }
        $out = [$s];
        $alt = str_replace(['台', '臺'], ['臺', '台'], $s);
        if ($alt !== $s && !in_array($alt, $out, true)) {
            $out[] = $alt;
        }

        return $out;
    }
}

if (!function_exists('fate_build_geocode_query')) {
    function fate_build_geocode_query(string $city, string $district, string $street): string
    {
        $parts = array_filter([
            fate_normalize_tw_address_part($street),
            fate_normalize_tw_address_part($district),
            fate_normalize_tw_address_part($city),
            '台灣',
        ], static fn (string $s): bool => $s !== '');

        return implode(', ', $parts);
    }
}

if (!function_exists('fate_osm_nominatim_base_url')) {
    function fate_osm_nominatim_base_url(): string
    {
        $base = 'https://nominatim.openstreetmap.org/search';
        if (defined('FATE_OSM_NOMINATIM_BASE') && is_string(FATE_OSM_NOMINATIM_BASE) && FATE_OSM_NOMINATIM_BASE !== '') {
            $base = rtrim(FATE_OSM_NOMINATIM_BASE, '/');
            if (substr($base, -7) !== '/search') {
                $base .= '/search';
            }
        }

        return $base;
    }
}

if (!function_exists('fate_osm_http_get')) {
    function fate_osm_http_get(string $url, array $headers): array
    {
        if (function_exists('curl_init')) {
            $ch = curl_init($url);
            curl_setopt_array($ch, [
                CURLOPT_HTTPHEADER => $headers,
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_FOLLOWLOCATION => true,
                CURLOPT_TIMEOUT => 20,
                CURLOPT_CONNECTTIMEOUT => 8,
                CURLOPT_SSL_VERIFYPEER => true,
                CURLOPT_SSL_VERIFYHOST => 2,
            ]);
            if (defined('FATE_OSM_CA_BUNDLE') && is_string(FATE_OSM_CA_BUNDLE) && FATE_OSM_CA_BUNDLE !== '' && is_file(FATE_OSM_CA_BUNDLE)) {
                curl_setopt($ch, CURLOPT_CAINFO, FATE_OSM_CA_BUNDLE);
            }
            $raw = curl_exec($ch);
            $http = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
            $err = curl_error($ch);
            curl_close($ch);
            if ($raw === false || $raw === '') {
                return ['ok' => false, 'http' => $http, 'error' => $err !== '' ? $err : 'empty response'];
            }

            return ['ok' => true, 'http' => $http, 'body' => (string) $raw];
        }

        $ctx = stream_context_create([
            'http' => [
                'timeout' => 20,
                'header' => implode("\r\n", $headers) . "\r\n",
            ],
        ]);
        $raw = @file_get_contents($url, false, $ctx);
        if ($raw === false) {
            return ['ok' => false, 'http' => 0, 'error' => '無法連線（請啟用 curl 或 allow_url_fopen）'];
        }

        return ['ok' => true, 'http' => 200, 'body' => (string) $raw];
    }
}

if (!function_exists('fate_osm_hit_from_nominatim_list')) {
    /**
     * @param list<array<string,mixed>> $list
     * @return array<string,mixed>|null
     */
    function fate_osm_hit_from_nominatim_list(array $list): ?array
    {
        foreach ($list as $hit) {
            if (!is_array($hit)) {
                continue;
            }
            $lat = isset($hit['lat']) ? (float) $hit['lat'] : 0.0;
            $lng = isset($hit['lon']) ? (float) $hit['lon'] : 0.0;
            if (is_finite($lat) && is_finite($lng) && abs($lat) <= 90 && abs($lng) <= 180) {
                return $hit;
            }
        }

        return null;
    }
}

if (!function_exists('fate_osm_format_hit')) {
    /**
     * @param array<string,mixed> $hit
     * @return array{ok:bool,lat:float,lng:float,display_name:string,google_map_location:string,precision?:string}
     */
    function fate_osm_format_hit(array $hit, string $precision = 'address'): array
    {
        $lat = (float) $hit['lat'];
        $lng = (float) $hit['lon'];
        $display = trim((string) ($hit['display_name'] ?? ''));
        $gml = number_format($lat, 6, '.', '') . ',' . number_format($lng, 6, '.', '');

        $out = [
            'ok' => true,
            'lat' => $lat,
            'lng' => $lng,
            'display_name' => $display,
            'google_map_location' => $gml,
            'precision' => $precision,
        ];
        if ($precision === 'district') {
            $out['display_name'] = ($display !== '' ? $display : '') . '（僅定位至縣市／行政區，請確認街道或手動微調 GPS）';
        }

        return $out;
    }
}

if (!function_exists('fate_osm_nominatim_request')) {
    /**
     * @param array<string,mixed> $params
     * @return array<string,mixed>|null
     */
    function fate_osm_nominatim_request(array $params): ?array
    {
        fate_osm_rate_limit_wait();

        $headers = [
            'Accept: application/json',
            'User-Agent: ' . fate_osm_nominatim_user_agent(),
        ];

        $query = [
            'format' => 'jsonv2',
            'limit' => 5,
            'addressdetails' => 1,
        ];

        if (($params['mode'] ?? '') === 'structured') {
            foreach (['street', 'city', 'county', 'state', 'country'] as $k) {
                if (!empty($params[$k]) && is_string($params[$k])) {
                    $query[$k] = $params[$k];
                }
            }
            if (empty($query['country'])) {
                $query['country'] = 'Taiwan';
            }
        } else {
            $q = trim((string) ($params['q'] ?? ''));
            if ($q === '') {
                return null;
            }
            $query['q'] = $q;
            if (!empty($params['countrycodes'])) {
                $query['countrycodes'] = (string) $params['countrycodes'];
            }
        }

        $url = fate_osm_nominatim_base_url() . '?' . http_build_query($query);
        $res = fate_osm_http_get($url, $headers);
        if (empty($res['ok'])) {
            return null;
        }
        if (($res['http'] ?? 0) !== 200 && ($res['http'] ?? 0) !== 0) {
            return null;
        }

        $list = json_decode((string) ($res['body'] ?? ''), true);

        return is_array($list) ? fate_osm_hit_from_nominatim_list($list) : null;
    }
}

if (!function_exists('fate_photon_geocode_search')) {
    /**
     * @return array<string,mixed>|null
     */
    function fate_photon_geocode_search(string $q): ?array
    {
        $q = trim($q);
        if ($q === '') {
            return null;
        }

        fate_osm_rate_limit_wait();

        $url = 'https://photon.komoot.io/api/?' . http_build_query([
            'q' => $q,
            'limit' => 1,
            'lang' => 'zh',
        ]);

        $headers = [
            'Accept: application/json',
            'User-Agent: ' . fate_osm_nominatim_user_agent(),
        ];

        $res = fate_osm_http_get($url, $headers);
        if (empty($res['ok'])) {
            return null;
        }

        $data = json_decode((string) ($res['body'] ?? ''), true);
        if (!is_array($data) || empty($data['features'][0]) || !is_array($data['features'][0])) {
            return null;
        }

        $feat = $data['features'][0];
        $coords = $feat['geometry']['coordinates'] ?? null;
        if (!is_array($coords) || count($coords) < 2) {
            return null;
        }
        $lng = (float) $coords[0];
        $lat = (float) $coords[1];
        if (!is_finite($lat) || !is_finite($lng)) {
            return null;
        }

        $name = trim((string) ($feat['properties']['name'] ?? ''));
        $city = trim((string) ($feat['properties']['city'] ?? ''));
        $state = trim((string) ($feat['properties']['state'] ?? ''));
        $display = trim(implode(', ', array_filter([$name, $city, $state, '台灣'])));

        return [
            'lat' => (string) $lat,
            'lon' => (string) $lng,
            'display_name' => $display !== '' ? $display : $q,
        ];
    }
}

if (!function_exists('fate_osm_tw_search_attempts')) {
    /**
     * @return list<array<string,mixed>>
     */
    function fate_osm_tw_search_attempts(string $city, string $district, string $street): array
    {
        $city = fate_normalize_tw_address_part($city);
        $district = fate_normalize_tw_address_part($district);
        $street = fate_normalize_tw_address_part($street);

        $attempts = [];
        $c0 = fate_tw_text_variants($city)[0] ?? $city;
        $d0 = fate_tw_text_variants($district)[0] ?? $district;

        if ($street !== '' && $c0 !== '' && $d0 !== '') {
            $attempts[] = [
                'mode' => 'structured',
                'street' => $street,
                'city' => $d0,
                'county' => $c0,
                'country' => 'Taiwan',
                'precision' => 'address',
            ];
            $attempts[] = [
                'mode' => 'search',
                'q' => implode(', ', [$street, $d0, $c0, '台灣']),
                'precision' => 'address',
            ];
            $attempts[] = [
                'mode' => 'search',
                'q' => $c0 . $d0 . $street,
                'precision' => 'address',
            ];
            $attempts[] = [
                'mode' => 'search',
                'q' => $c0 . $d0 . $street,
                'precision' => 'address',
                'countrycodes' => 'tw',
            ];
        }

        if ($c0 !== '' && $d0 !== '') {
            $attempts[] = [
                'mode' => 'search',
                'q' => implode(', ', [$d0, $c0, '台灣']),
                'precision' => 'district',
            ];
        }

        // 臺／台 另一寫法（僅一組）
        $cAlt = fate_tw_text_variants($city)[1] ?? null;
        $dAlt = fate_tw_text_variants($district)[1] ?? null;
        if ($street !== '' && ($cAlt !== null || $dAlt !== null)) {
            $c = $cAlt ?? $c0;
            $d = $dAlt ?? $d0;
            $attempts[] = [
                'mode' => 'search',
                'q' => implode(', ', [$street, $d, $c, '台灣']),
                'precision' => 'address',
            ];
        }

        // 去重（依序列化）
        $seen = [];
        $unique = [];
        foreach ($attempts as $a) {
            $key = json_encode($a, JSON_UNESCAPED_UNICODE);
            if (isset($seen[$key])) {
                continue;
            }
            $seen[$key] = true;
            $unique[] = $a;
        }

        return $unique;
    }
}

if (!function_exists('fate_osm_geocode_address')) {
    /**
     * @return array{ok:bool,lat?:float,lng?:float,display_name?:string,google_map_location?:string,error?:string,precision?:string}
     */
    function fate_osm_geocode_address(string $city, string $district, string $street): array
    {
        $city = fate_normalize_tw_address_part($city);
        $district = fate_normalize_tw_address_part($district);
        $street = fate_normalize_tw_address_part($street);

        if ($city === '' && $district === '' && $street === '') {
            return ['ok' => false, 'error' => '請輸入較完整的地址'];
        }

        $attempts = fate_osm_tw_search_attempts($city, $district, $street);
        $photonQueries = [];

        foreach ($attempts as $attempt) {
            $precision = (string) ($attempt['precision'] ?? 'address');
            $params = $attempt;
            unset($params['precision']);

            $hit = fate_osm_nominatim_request($params);
            if ($hit !== null) {
                return fate_osm_format_hit($hit, $precision);
            }

            if (($params['mode'] ?? '') === 'search' && !empty($params['q'])) {
                $photonQueries[] = (string) $params['q'];
            }
        }

        $photonQueries = array_slice(array_values(array_unique($photonQueries)), 0, 3);
        foreach ($photonQueries as $pq) {
            $hit = fate_photon_geocode_search($pq);
            if ($hit !== null) {
                $prec = ($street !== '' && strpos($pq, $street) !== false) ? 'address' : 'district';

                return fate_osm_format_hit($hit, $prec);
            }
        }

        return [
            'ok' => false,
            'error' => '找不到符合的座標。請確認街道是否完整（建議含路名與門牌號），或改以「緯度,經度」手動填入 GPS。',
        ];
    }
}

if (!function_exists('fate_osm_geocode_search')) {
    /**
     * 自由文字查詢（相容舊呼叫）
     *
     * @return array{ok:bool,lat?:float,lng?:float,display_name?:string,google_map_location?:string,error?:string,precision?:string}
     */
    function fate_osm_geocode_search(string $query): array
    {
        $query = trim($query);
        if (mb_strlen($query, 'UTF-8') < 3) {
            return ['ok' => false, 'error' => '請輸入較完整的地址'];
        }

        fate_osm_rate_limit_wait();
        $hit = fate_osm_nominatim_request(['mode' => 'search', 'q' => $query, 'countrycodes' => 'tw']);
        if ($hit !== null) {
            return fate_osm_format_hit($hit, 'address');
        }

        $hit = fate_osm_nominatim_request(['mode' => 'search', 'q' => $query]);
        if ($hit !== null) {
            return fate_osm_format_hit($hit, 'address');
        }

        $hit = fate_photon_geocode_search($query);
        if ($hit !== null) {
            return fate_osm_format_hit($hit, 'address');
        }

        return [
            'ok' => false,
            'error' => '找不到符合的座標。請檢查地址或改寫後再試，亦可手動填入 GPS（緯度,經度）。',
        ];
    }
}
