<?php

declare(strict_types=1);

/**
 * 全站 SEO：meta description、canonical、Open Graph、JSON-LD。
 */

require_once __DIR__ . DIRECTORY_SEPARATOR . 'fate_member_email.php';
require_once __DIR__ . DIRECTORY_SEPARATOR . 'fate_ui_settings.php';

function fate_seo_trim_description(string $text, int $max = 160): string
{
    $text = preg_replace('/\s+/u', ' ', trim(strip_tags($text))) ?? '';
    if ($text === '') {
        return '';
    }
    if (mb_strlen($text) > $max) {
        return rtrim(mb_substr($text, 0, $max - 1)) . '…';
    }

    return $text;
}

function fate_seo_site_default_description(): string
{
    $brand = fate_public_site_branding();
    $main = trim((string) ($brand['title_main'] ?? 'FATE'));
    $sub = trim((string) ($brand['title_sub'] ?? '店家探索'));

    return fate_seo_trim_description(
        $main . ' — ' . $sub . '。瀏覽與探索店家資訊、評分、地址與服務區域，查找優質 SPA 與按摩店家。',
        160
    );
}

/** @param array<string, scalar|null> $params */
function fate_seo_build_query_canonical(string $script, array $params): string
{
    $pairs = [];
    foreach ($params as $k => $v) {
        if ($v === null || $v === '') {
            continue;
        }
        $pairs[] = rawurlencode((string) $k) . '=' . rawurlencode((string) $v);
    }
    $path = $script . ($pairs !== [] ? '?' . implode('&', $pairs) : '');

    return fate_absolute_url($path);
}

function fate_seo_resolve_image_url(?string $url): ?string
{
    if ($url === null || trim($url) === '') {
        return null;
    }
    $u = trim($url);
    if (preg_match('#^https?://#i', $u) === 1) {
        return $u;
    }
    $u = ltrim(str_replace('\\', '/', $u), '/');
    $u = preg_replace('#^\./+#', '', $u) ?? $u;

    return fate_absolute_url($u);
}

/**
 * @param array<string,mixed>|null $context
 * @return array<string,mixed>
 */
function fate_seo_normalize(?array $context, string $pageTitle): array
{
    $ctx = is_array($context) ? $context : [];
    $title = trim($pageTitle);
    if ($title === '' && isset($ctx['title']) && is_string($ctx['title'])) {
        $title = trim($ctx['title']);
    }

    $description = isset($ctx['description']) && is_string($ctx['description']) && trim($ctx['description']) !== ''
        ? fate_seo_trim_description($ctx['description'])
        : fate_seo_site_default_description();

    $canonical = isset($ctx['canonical']) && is_string($ctx['canonical']) && trim($ctx['canonical']) !== ''
        ? trim($ctx['canonical'])
        : fate_seo_current_request_canonical();

    $robots = isset($ctx['robots']) && is_string($ctx['robots']) && trim($ctx['robots']) !== ''
        ? trim($ctx['robots'])
        : 'index,follow';

    $ogType = isset($ctx['og_type']) && is_string($ctx['og_type']) && trim($ctx['og_type']) !== ''
        ? trim($ctx['og_type'])
        : 'website';

    $relPrev = isset($ctx['rel_prev']) && is_string($ctx['rel_prev']) ? trim($ctx['rel_prev']) : '';
    $relNext = isset($ctx['rel_next']) && is_string($ctx['rel_next']) ? trim($ctx['rel_next']) : '';

    $ogImage = null;
    if (isset($ctx['og_image']) && is_string($ctx['og_image'])) {
        $ogImage = fate_seo_resolve_image_url($ctx['og_image']);
    }

    $jsonLd = [];
    if (isset($ctx['json_ld'])) {
        if (is_array($ctx['json_ld']) && isset($ctx['json_ld']['@context'])) {
            $jsonLd = [$ctx['json_ld']];
        } elseif (is_array($ctx['json_ld'])) {
            foreach ($ctx['json_ld'] as $one) {
                if (is_array($one) && isset($one['@context'])) {
                    $jsonLd[] = $one;
                }
            }
        }
    }

    return [
        'title' => $title,
        'description' => $description,
        'canonical' => $canonical,
        'robots' => $robots,
        'og_type' => $ogType,
        'og_image' => $ogImage,
        'rel_prev' => $relPrev,
        'rel_next' => $relNext,
        'json_ld' => $jsonLd,
    ];
}

/** DB datetime → ISO8601（供 schema.org datePublished 等）；無效回 null。 */
function fate_seo_iso_datetime(?string $dbDatetime): ?string
{
    $s = trim((string) $dbDatetime);
    if ($s === '' || $s === '0000-00-00 00:00:00') {
        return null;
    }
    try {
        $tz = new DateTimeZone(date_default_timezone_get());

        return (new DateTimeImmutable($s, $tz))->format('c');
    } catch (Throwable) {
        return null;
    }
}

function fate_seo_current_request_canonical(): string
{
    $script = basename((string) ($_SERVER['SCRIPT_NAME'] ?? 'index.php'));
    if ($script === '') {
        $script = 'index.php';
    }
    $allowed = ['id', 'listing', 'city', 'sort', 'q', 'page', 'section', 'store_id', 'masseur_id'];
    $params = [];
    foreach ($allowed as $key) {
        if (!isset($_GET[$key])) {
            continue;
        }
        $val = trim((string) $_GET[$key]);
        if ($val === '' || ($key === 'page' && $val === '1')) {
            continue;
        }
        if ($key === 'sort' && $val === 'rating') {
            continue;
        }
        $params[$key] = $val;
    }

    return fate_seo_build_query_canonical($script, $params);
}

/** @param array<string,mixed> $ctx */
function fate_seo_render(?array $context, string $pageTitle): void
{
    $ctx = fate_seo_normalize($context, $pageTitle);
    $brand = fate_public_site_branding();
    $siteName = trim((string) ($brand['title_main'] ?? 'FATE'));

    echo '    <meta name="description" content="' . htmlspecialchars($ctx['description'], ENT_QUOTES, 'UTF-8') . '">' . "\n";
    echo '    <meta name="robots" content="' . htmlspecialchars($ctx['robots'], ENT_QUOTES, 'UTF-8') . '">' . "\n";
    echo '    <link rel="canonical" href="' . htmlspecialchars($ctx['canonical'], ENT_QUOTES, 'UTF-8') . '">' . "\n";
    if (($ctx['rel_prev'] ?? '') !== '') {
        echo '    <link rel="prev" href="' . htmlspecialchars($ctx['rel_prev'], ENT_QUOTES, 'UTF-8') . '">' . "\n";
    }
    if (($ctx['rel_next'] ?? '') !== '') {
        echo '    <link rel="next" href="' . htmlspecialchars($ctx['rel_next'], ENT_QUOTES, 'UTF-8') . '">' . "\n";
    }

    echo '    <meta property="og:locale" content="zh_TW">' . "\n";
    echo '    <meta property="og:site_name" content="' . htmlspecialchars($siteName, ENT_QUOTES, 'UTF-8') . '">' . "\n";
    echo '    <meta property="og:title" content="' . htmlspecialchars($ctx['title'], ENT_QUOTES, 'UTF-8') . '">' . "\n";
    echo '    <meta property="og:description" content="' . htmlspecialchars($ctx['description'], ENT_QUOTES, 'UTF-8') . '">' . "\n";
    echo '    <meta property="og:url" content="' . htmlspecialchars($ctx['canonical'], ENT_QUOTES, 'UTF-8') . '">' . "\n";
    echo '    <meta property="og:type" content="' . htmlspecialchars($ctx['og_type'], ENT_QUOTES, 'UTF-8') . '">' . "\n";

    echo '    <meta name="twitter:card" content="' . ($ctx['og_image'] !== null ? 'summary_large_image' : 'summary') . '">' . "\n";
    echo '    <meta name="twitter:title" content="' . htmlspecialchars($ctx['title'], ENT_QUOTES, 'UTF-8') . '">' . "\n";
    echo '    <meta name="twitter:description" content="' . htmlspecialchars($ctx['description'], ENT_QUOTES, 'UTF-8') . '">' . "\n";

    if ($ctx['og_image'] !== null) {
        echo '    <meta property="og:image" content="' . htmlspecialchars($ctx['og_image'], ENT_QUOTES, 'UTF-8') . '">' . "\n";
        echo '    <meta name="twitter:image" content="' . htmlspecialchars($ctx['og_image'], ENT_QUOTES, 'UTF-8') . '">' . "\n";
    }

    foreach ($ctx['json_ld'] as $block) {
        if (!is_array($block)) {
            continue;
        }
        $json = json_encode($block, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        if (!is_string($json)) {
            continue;
        }
        echo '    <script type="application/ld+json">' . $json . '</script>' . "\n";
    }
}

/** @return array<string,mixed> */
function fate_seo_private_page_context(): array
{
    return ['robots' => 'noindex,nofollow'];
}

/** @return array<string,mixed> */
function fate_seo_simple_page_context(string $title, string $description = ''): array
{
    $desc = fate_seo_trim_description($description);

    return [
        'title' => $title,
        'description' => $desc !== '' ? $desc : fate_seo_site_default_description(),
    ];
}

/** @return array<string,mixed> */
function fate_seo_website_json_ld(?array $brand = null): array
{
    $brand = $brand ?? fate_public_site_branding();
    $name = trim((string) ($brand['title_main'] ?? 'FATE'));
    $home = fate_absolute_url('index.php');

    return [
        '@context' => 'https://schema.org',
        '@type' => 'WebSite',
        'name' => $name,
        'url' => $home,
        'inLanguage' => 'zh-TW',
        'potentialAction' => [
            '@type' => 'SearchAction',
            'target' => fate_absolute_url('index.php') . '?q={search_term_string}',
            'query-input' => 'required name=search_term_string',
        ],
    ];
}

/**
 * @param array{
 *   listing?:string,
 *   city?:string,
 *   q?:string,
 *   page?:int,
 *   sort?:string,
 *   storeTotal?:int
 * } $params
 * @return array<string,mixed>
 */
function fate_seo_home_context(array $params): array
{
    require_once __DIR__ . DIRECTORY_SEPARATOR . 'fate_store_listing.php';

    $brand = fate_public_site_branding();
    $listing = fate_store_listing_normalize_tab((string) ($params['listing'] ?? FATE_STORE_LISTING_FIXED));
    $city = trim((string) ($params['city'] ?? ''));
    $q = trim((string) ($params['q'] ?? ''));
    $page = max(1, (int) ($params['page'] ?? 1));
    $sort = (string) ($params['sort'] ?? 'rating');
    $total = max(0, (int) ($params['storeTotal'] ?? 0));
    $listingLabel = fate_store_listing_tab_label($listing);

    $titleParts = [$brand['title_main'], $listingLabel];
    if ($city !== '') {
        $titleParts[] = $city;
    }
    if ($q !== '') {
        $titleParts[] = '搜尋「' . $q . '」';
    }
    if ($page > 1) {
        $titleParts[] = '第 ' . $page . ' 頁';
    }
    if (count($titleParts) === 2 && $titleParts[1] === fate_store_listing_tab_label(FATE_STORE_LISTING_FIXED)) {
        $title = $brand['title_main'] . ' — ' . $brand['title_sub'];
    } else {
        $title = implode(' — ', $titleParts);
    }

    $descBits = ['瀏覽' . $listingLabel . '列表'];
    if ($city !== '') {
        $descBits[] = '篩選 ' . $city;
    }
    if ($q !== '') {
        $descBits[] = '關鍵字「' . $q . '」';
    }
    if ($total > 0) {
        $descBits[] = '共 ' . number_format($total) . ' 筆';
    }
    $description = fate_seo_trim_description(implode('，', $descBits) . '。' . $brand['title_sub']);

    return [
        'title' => $title,
        'description' => $description,
        'canonical' => fate_seo_build_query_canonical('index.php', [
            'listing' => $listing === FATE_STORE_LISTING_MOBILE ? 'mobile' : null,
            'city' => $city !== '' ? $city : null,
            'sort' => $sort !== 'rating' ? $sort : null,
            'q' => $q !== '' ? $q : null,
            'page' => $page > 1 ? (string) $page : null,
        ]),
        'og_type' => 'website',
        'json_ld' => [fate_seo_website_json_ld($brand)],
    ];
}

/**
 * @param array<string,mixed>|null $row
 * @return array<string,mixed>
 */
function fate_seo_store_context(?array $row, int $storeId): array
{
    require_once __DIR__ . DIRECTORY_SEPARATOR . 'fate_store_listing.php';
    require_once __DIR__ . DIRECTORY_SEPARATOR . 'fate_public_urls.php';
    require_once __DIR__ . DIRECTORY_SEPARATOR . 'fate_member_stores.php';

    $brand = fate_public_site_branding();
    $siteName = trim((string) ($brand['title_main'] ?? 'FATE'));

    if ($row === null || $storeId <= 0) {
        return [
            'title' => ($storeId > 0 ? '找不到店家' : '店家詳情') . ' — ' . $siteName,
            'description' => fate_seo_site_default_description(),
            'canonical' => fate_absolute_url('store.php?id=' . max(0, $storeId)),
            'robots' => $storeId > 0 ? 'noindex,nofollow' : 'noindex,nofollow',
        ];
    }

    $name = trim((string) ($row['store_name'] ?? ''));
    if ($name === '') {
        $name = '店家 #' . $storeId;
    }

    $location = fate_store_listing_location_text($row);
    $city = trim((string) ($row['city_name'] ?? ''));
    $district = trim((string) ($row['district_name'] ?? ''));
    $phone = trim((string) ($row['phone'] ?? ''));

    $descBits = [$name];
    if ($location !== '') {
        $descBits[] = $location;
    }
    $rating = isset($row['place_rating']) && $row['place_rating'] !== '' && $row['place_rating'] !== null
        ? (float) $row['place_rating']
        : null;
    $rCount = isset($row['place_ratings_total']) ? (int) $row['place_ratings_total'] : 0;
    if ($rating !== null) {
        $descBits[] = 'Google 評分 ' . number_format($rating, 1) . ($rCount > 0 ? '（' . $rCount . ' 則評價）' : '');
    }
    $description = fate_seo_trim_description(implode(' · ', $descBits));

    $cover = fate_store_cover_src(
        isset($row['cover_photo_path']) ? (string) $row['cover_photo_path'] : null,
        isset($row['photos_json']) ? (string) $row['photos_json'] : null
    );
    $imgRel = fate_spa_asset_url($cover['path']);
    $ogImage = $imgRel !== null ? fate_seo_resolve_image_url($imgRel) : null;

    $jsonLd = [
        '@context' => 'https://schema.org',
        '@type' => 'HealthAndBeautyBusiness',
        'name' => $name,
        'url' => fate_absolute_url('store.php?id=' . $storeId),
    ];
    if ($location !== '') {
        $jsonLd['address'] = [
            '@type' => 'PostalAddress',
            'addressLocality' => trim($city . $district),
            'streetAddress' => $location,
            'addressCountry' => 'TW',
        ];
    }
    if ($phone !== '') {
        $jsonLd['telephone'] = $phone;
    }
    if ($rating !== null && $rating > 0 && $rCount > 0) {
        $jsonLd['aggregateRating'] = [
            '@type' => 'AggregateRating',
            'ratingValue' => round($rating, 1),
            'bestRating' => 5,
            'ratingCount' => $rCount,
        ];
    }
    if ($ogImage !== null) {
        $jsonLd['image'] = $ogImage;
    }

    return [
        'title' => $name . ' — ' . $siteName,
        'description' => $description,
        'canonical' => fate_absolute_url('store.php?id=' . $storeId),
        'og_type' => 'website',
        'og_image' => $ogImage,
        'json_ld' => [$jsonLd],
    ];
}

/** @return array<string,mixed> */
function fate_seo_forum_index_context(?array $board, string $cityFilter): array
{
    $brand = fate_public_site_branding();
    $title = '討論區 — ' . $brand['title_main'];
    $desc = '參與 ' . $brand['title_main'] . ' 討論區，瀏覽綜合與店家話題、分享體驗與店況回報。';
    $section = $board !== null ? (string) ($board['slug'] ?? '') : '';
    $boardTitle = $board !== null ? trim((string) ($board['title'] ?? '')) : '';
    $boardDesc = $board !== null ? trim((string) ($board['description'] ?? '')) : '';

    if ($board !== null && (string) ($board['kind'] ?? '') === 'store') {
        $title = ($boardTitle !== '' ? $boardTitle : '店家討論') . ' — ' . $brand['title_main'];
        $desc = $boardDesc !== '' ? $boardDesc : '瀏覽各店家的討論主題，查看網友分享與最新回覆。';
    } elseif ($board !== null && (string) ($board['kind'] ?? '') === 'general') {
        $title = ($boardTitle !== '' ? $boardTitle : '一般討論') . ' — ' . $brand['title_main'];
        $desc = $boardDesc !== '' ? $boardDesc : '瀏覽與參與 ' . ($boardTitle !== '' ? $boardTitle : '一般討論') . ' 版塊的主題與回覆。';
    }
    if ($cityFilter !== '') {
        $title = $cityFilter . ' 店家討論 — ' . $brand['title_main'];
        $desc = '瀏覽 ' . $cityFilter . ' 地區店家討論主題與回覆。';
    }

    $forumHome = fate_absolute_url('forum/index.php');
    $crumbs = [[
        '@type' => 'ListItem',
        'position' => 1,
        'name' => '討論區',
        'item' => $forumHome,
    ]];
    if ($boardTitle !== '' && $section !== '') {
        $crumbs[] = [
            '@type' => 'ListItem',
            'position' => 2,
            'name' => $boardTitle,
            'item' => fate_seo_build_query_canonical('forum/index.php', ['section' => $section]),
        ];
    } elseif ($cityFilter !== '') {
        $crumbs[] = [
            '@type' => 'ListItem',
            'position' => 2,
            'name' => $cityFilter . ' 店家討論',
            'item' => fate_seo_build_query_canonical('forum/index.php', ['city' => $cityFilter]),
        ];
    }

    return [
        'title' => $title,
        'description' => fate_seo_trim_description($desc),
        'canonical' => fate_seo_build_query_canonical('forum/index.php', [
            'section' => $section !== '' ? $section : null,
            'city' => $cityFilter !== '' ? $cityFilter : null,
        ]),
        'og_type' => 'website',
        'json_ld' => [[
            '@context' => 'https://schema.org',
            '@type' => 'BreadcrumbList',
            'itemListElement' => $crumbs,
        ]],
    ];
}

/**
 * @param array<string,mixed> $thread
 * @param array<string,mixed>|null $firstPost
 * @param array<string,mixed>|null $board
 * @return array<string,mixed>
 */
function fate_seo_forum_thread_context(array $thread, ?array $firstPost, int $threadId, ?array $board = null): array
{
    $brand = fate_public_site_branding();
    $threadTitle = trim((string) ($thread['title'] ?? '討論主題'));
    $title = $threadTitle . ' — ' . $brand['title_main'] . ' 討論區';
    $canonical = fate_absolute_url('forum/thread.php?id=' . $threadId);

    $body = is_array($firstPost) ? (string) ($firstPost['body'] ?? '') : '';
    $description = $body !== ''
        ? fate_seo_trim_description($body)
        : fate_seo_trim_description('討論主題：' . $threadTitle);

    $authorName = trim((string) ($thread['author_name'] ?? ($firstPost['author_name'] ?? '')));
    $published = fate_seo_iso_datetime((string) ($thread['created_at'] ?? ($firstPost['created_at'] ?? '')));
    $modified = fate_seo_iso_datetime((string) ($thread['last_post_at'] ?? '')) ?? $published;
    $replyCount = isset($thread['reply_count']) ? max(0, (int) $thread['reply_count']) : null;

    $posting = [
        '@context' => 'https://schema.org',
        '@type' => 'DiscussionForumPosting',
        'headline' => $threadTitle,
        'url' => $canonical,
        'mainEntityOfPage' => $canonical,
        'inLanguage' => 'zh-TW',
        'publisher' => [
            '@type' => 'Organization',
            'name' => trim((string) ($brand['title_main'] ?? 'FATE')),
            'url' => fate_absolute_url('index.php'),
        ],
    ];
    if ($description !== '') {
        $posting['articleBody'] = $description;
    }
    if ($authorName !== '') {
        $posting['author'] = ['@type' => 'Person', 'name' => $authorName];
    }
    if ($published !== null) {
        $posting['datePublished'] = $published;
    }
    if ($modified !== null) {
        $posting['dateModified'] = $modified;
    }
    if ($replyCount !== null) {
        $posting['commentCount'] = $replyCount;
        $posting['interactionStatistic'] = [
            '@type' => 'InteractionCounter',
            'interactionType' => 'https://schema.org/CommentAction',
            'userInteractionCount' => $replyCount,
        ];
    }

    $boardTitle = $board !== null ? trim((string) ($board['title'] ?? '')) : '';
    $boardSlug = $board !== null ? trim((string) ($board['slug'] ?? '')) : '';
    $crumbs = [[
        '@type' => 'ListItem',
        'position' => 1,
        'name' => '討論區',
        'item' => fate_absolute_url('forum/index.php'),
    ]];
    $pos = 2;
    if ($boardTitle !== '' && $boardSlug !== '') {
        $crumbs[] = [
            '@type' => 'ListItem',
            'position' => $pos++,
            'name' => $boardTitle,
            'item' => fate_seo_build_query_canonical('forum/index.php', ['section' => $boardSlug]),
        ];
    }
    $crumbs[] = [
        '@type' => 'ListItem',
        'position' => $pos,
        'name' => $threadTitle,
        'item' => $canonical,
    ];

    $ctx = [
        'title' => $title,
        'description' => $description,
        'canonical' => $canonical,
        'og_type' => 'article',
        'json_ld' => [
            $posting,
            [
                '@context' => 'https://schema.org',
                '@type' => 'BreadcrumbList',
                'itemListElement' => $crumbs,
            ],
        ],
    ];
    if ((int) ($thread['is_hidden'] ?? 0) === 1) {
        $ctx['robots'] = 'noindex,nofollow';
    }

    return $ctx;
}
