<?php

declare(strict_types=1);

namespace Acms\Services\Url;

use Uri\WhatWg\Url as WhatWgUrl;

/**
 * メディアの配信先URL（ASSETS_DELIVERY_URL）への書き換えを担う。
 */
class DeliveryUrlRewriter
{
    /**
     * メディアの配信URL
     *
     * @var string
     */
    private $mediaDeliveryUrl = '';

    /**
     * @var ManagedDomainRegistry
     */
    private $managedDomainRegistry;

    /**
     * @param ManagedDomainRegistry $managedDomainRegistry
     */
    public function __construct(ManagedDomainRegistry $managedDomainRegistry)
    {
        $this->managedDomainRegistry = $managedDomainRegistry;

        $mediaDeliveryUrl = env('ASSETS_DELIVERY_URL', '');
        if (!!$mediaDeliveryUrl) {
            $this->mediaDeliveryUrl = rtrim($mediaDeliveryUrl, '/');
        }
    }

    /**
     * 指定URLが管理ドメインからのものかどうかを判定
     *
     * @param string $url
     * @return bool
     */
    private function isUrlFromManagedDomain(string $url): bool
    {
        // WHATWG パーサーを使うことで日本語パスを含む絶対URLも正しくホスト抽出できる。
        // 相対URLは parse() が null を返すため、自ドメインとみなす既存の動作が維持される。
        $host = WhatWgUrl::parse($url)?->getAsciiHost();
        if ($host === null) {
            return true; // 相対URLは自ドメインとみなす
        }
        // HTTP_HOSTを含めてチェック（相対URLの場合は自ドメインとみなすため）
        return in_array($host, array_map('strtolower', $this->managedDomainRegistry->getManagedDomains([HTTP_HOST])), true);
    }

    /**
     * メディアの配信先URLを書き換え
     *
     * @param string $url
     * @return string
     */
    public function replaceDeliveryUrl(string $url): string
    {
        if (!$this->mediaDeliveryUrl) {
            return $url;
        }
        // 管理ドメイン以外は置換しない
        if (!$this->isUrlFromManagedDomain($url)) {
            return $url;
        }
        $mediaDeliveryUrl = rtrim($this->mediaDeliveryUrl, '/');
        // 安全にディレクトリ名を正規表現化（前後の / を吸収）
        $dirs = implode('|', array_map(
            fn($d) => preg_quote(trim($d, '/'), '~'),
            [MEDIA_LIBRARY_DIR, ARCHIVES_DIR]
        ));
        // 例: https://mydomain.com/hoge/media/... や /hoge/media/... もOK
        // 末尾の ?query や #hash も保持
        // 先行する任意のサブディレクトリは「ターゲットDIRではない」ことを保証してスキップ
        // その後で "/(media|archives)/..." を $1 にキャプチャ。?query と #hash は $2
        $pattern = '~(?:https?://[^/]+)?(?:(?:/(?!' . $dirs . ')(?:[^/?#]+))*)'  // 前置きパス（ただし target dir ではない）
            . '(/(?:' . $dirs . ')/[^?#\s"\']*)' // ← ここからを置換対象として $1
            . '([?#][^\s"\']*)?' // クエリ/ハッシュ（任意）
            . '~iu';
        $replacement = $mediaDeliveryUrl . '$1$2';

        return preg_replace($pattern, $replacement, $url) ?? $url;
    }

    /**
     * メディアの配信先URLを書き換え（全て）
     *
     * @param string $html
     * @return string
     */
    public function replaceDeliveryUrlAll(string $html): string
    {
        if (!$this->mediaDeliveryUrl) {
            return $html;
        }

        // 1) 単一URL属性（href/src/poster/data-src/data-original）
        $attrPattern = '~\b(href|content|src|poster|data-src|data-original)\s*=\s*(["\'])(.*?)\2~i';
        $html = preg_replace_callback($attrPattern, function ($m) {
            [$full, $attr, $q, $val] = $m;

            // javascript:, mailto:, data: はスキップ
            if (preg_match('~^(?:javascript:|mailto:|data:)~iu', $val)) {
                return $full;
            }
            $new = $this->replaceDeliveryUrl($val);
            return $attr . '=' . $q . $new . $q;
        }, $html) ?? $html;

        // 2) srcset（複数URL: "url size, url size, ..."）
        $srcsetPattern = '~\bsrcset\s*=\s*(["\'])(.*?)\1~iu';
        $html = preg_replace_callback($srcsetPattern, function ($m) {
            $q = $m[1];
            $list = $m[2];

            $items = array_map('trim', explode(',', $list));
            $items = array_map(function ($item) {
                // "URL [descriptor]" に分解（descriptor は省略可）
                // 先頭の1トークンをURLとみなす
                if ($item === '') {
                    return $item;
                }
                $parts = preg_split('/\s+/', $item, 2);
                $url = $parts[0];
                $desc = $parts[1] ?? '';
                $url = $this->replaceDeliveryUrl($url);
                return trim($url . ' ' . $desc);
            }, $items);

            return 'srcset=' . $q . implode(', ', $items) . $q;
        }, $html) ?? $html;

        // 3) style属性の url(...)
        $styleAttrPattern = '~\bstyle\s*=\s*(["\'])(.*?)\1~is';
        $html = preg_replace_callback($styleAttrPattern, function ($m) {
            $q = $m[1];
            $css = $m[2];
            $css = preg_replace_callback('~url\(\s*(["\']?)([^)\'"]+)\1\s*\)~iu', function ($n) {
                $u = $n[2];
                // data: はスキップ
                if (preg_match('~^data:~i', $u)) {
                    return $n[0];
                }
                $u = $this->replaceDeliveryUrl($u);
                return 'url(' . $u . ')';
            }, $css) ?? $css;
            return 'style=' . $q . $css . $q;
        }, $html) ?? $html;

        return $html;
    }
}
