<?php

namespace XZD\AI\V5\Tools;

/**
 * 内容对比引擎 — 行级和词级 Diff
 *
 * 支持 line（按行对比，适合段落级变更）和 word（按词对比，适合措辞修改）两种模式。
 * 使用最长公共子序列 (LCS) 算法。
 */
class DiffEngine
{
    /**
     * 执行 diff 并返回结构化结果
     *
     * @return array [{type: 'equal'|'add'|'remove', content: string}]
     */
    public static function diff(string $original, string $modified, string $mode = 'line'): array
    {
        if ($mode === 'word') {
            return self::wordDiff($original, $modified);
        }
        return self::lineDiff($original, $modified);
    }

    /**
     * 行级 diff
     */
    public static function lineDiff(string $original, string $modified): array
    {
        $origLines = explode("\n", $original);
        $modLines  = explode("\n", $modified);

        $lcs = self::computeLCS($origLines, $modLines);
        $result = [];
        $oi = 0;
        $mi = 0;

        foreach ($lcs as $common) {
            // 删除的行
            while ($oi < count($origLines) && $origLines[$oi] !== $common) {
                $result[] = ['type' => 'remove', 'content' => $origLines[$oi]];
                $oi++;
            }
            // 新增的行
            while ($mi < count($modLines) && $modLines[$mi] !== $common) {
                $result[] = ['type' => 'add', 'content' => $modLines[$mi]];
                $mi++;
            }
            // 相同的行
            $result[] = ['type' => 'equal', 'content' => $common];
            $oi++;
            $mi++;
        }

        // 末尾剩余
        while ($oi < count($origLines)) {
            $result[] = ['type' => 'remove', 'content' => $origLines[$oi]];
            $oi++;
        }
        while ($mi < count($modLines)) {
            $result[] = ['type' => 'add', 'content' => $modLines[$mi]];
            $mi++;
        }

        return self::mergeConsecutive($result);
    }

    /**
     * 词级 diff（中文友好：按字符切分）
     */
    public static function wordDiff(string $original, string $modified): array
    {
        // 将文本切分为词/字符数组
        $origWords = self::tokenize($original);
        $modWords  = self::tokenize($modified);

        $lcs = self::computeLCS($origWords, $modWords);
        $result = [];
        $oi = 0;
        $mi = 0;

        foreach ($lcs as $common) {
            // 删除的词
            if ($oi < count($origWords) && $origWords[$oi] !== $common) {
                $removed = [];
                while ($oi < count($origWords) && $origWords[$oi] !== $common) {
                    $removed[] = $origWords[$oi];
                    $oi++;
                }
                $result[] = ['type' => 'remove', 'content' => implode('', $removed)];
            }
            // 新增的词
            if ($mi < count($modWords) && $modWords[$mi] !== $common) {
                $added = [];
                while ($mi < count($modWords) && $modWords[$mi] !== $common) {
                    $added[] = $modWords[$mi];
                    $mi++;
                }
                $result[] = ['type' => 'add', 'content' => implode('', $added)];
            }
            $result[] = ['type' => 'equal', 'content' => $common];
            $oi++;
            $mi++;
        }

        // 末尾剩余
        if ($oi < count($origWords)) {
            $removed = [];
            while ($oi < count($origWords)) { $removed[] = $origWords[$oi]; $oi++; }
            $result[] = ['type' => 'remove', 'content' => implode('', $removed)];
        }
        if ($mi < count($modWords)) {
            $added = [];
            while ($mi < count($modWords)) { $added[] = $modWords[$mi]; $mi++; }
            $result[] = ['type' => 'add', 'content' => implode('', $added)];
        }

        return self::mergeConsecutive($result);
    }

    /**
     * 生成 HTML 对比视图
     */
    public static function toHtml(array $diffResult): string
    {
        $html = '<div class="xzd-diff-viewer">';
        foreach ($diffResult as $chunk) {
            $content = esc_html($chunk['content']);
            if (empty(trim($content))) continue;

            $class = match ($chunk['type']) {
                'add'    => 'xzd-diff-add',
                'remove' => 'xzd-diff-remove',
                default  => 'xzd-diff-equal',
            };

            $prefix = match ($chunk['type']) {
                'add'    => '+ ',
                'remove' => '- ',
                default  => '  ',
            };

            $html .= "<div class=\"{$class}\">{$prefix}{$content}</div>";
        }
        $html .= '</div>';
        return $html;
    }

    /**
     * 生成统计摘要
     */
    public static function getStats(array $diffResult): array
    {
        $added = 0;
        $removed = 0;
        $unchanged = 0;

        foreach ($diffResult as $chunk) {
            match ($chunk['type']) {
                'add'    => $added += mb_strlen($chunk['content']),
                'remove' => $removed += mb_strlen($chunk['content']),
                default  => $unchanged += mb_strlen($chunk['content']),
            };
        }

        $total = $added + $removed + $unchanged;
        return [
            'added_chars'      => $added,
            'removed_chars'    => $removed,
            'unchanged_chars'  => $unchanged,
            'total_chars'      => $total,
            'change_pct'       => $total > 0 ? round(($added + $removed) / $total * 100, 1) : 0,
        ];
    }

    // 内部方法

    /**
     * 最长公共子序列
     */
    private static function computeLCS(array $a, array $b): array
    {
        $m = count($a);
        $n = count($b);

        // 构建 DP 表
        $dp = array_fill(0, $m + 1, array_fill(0, $n + 1, 0));
        for ($i = 1; $i <= $m; $i++) {
            for ($j = 1; $j <= $n; $j++) {
                if ($a[$i - 1] === $b[$j - 1]) {
                    $dp[$i][$j] = $dp[$i - 1][$j - 1] + 1;
                } else {
                    $dp[$i][$j] = max($dp[$i - 1][$j], $dp[$i][$j - 1]);
                }
            }
        }

        // 回溯
        $lcs = [];
        $i = $m;
        $j = $n;
        while ($i > 0 && $j > 0) {
            if ($a[$i - 1] === $b[$j - 1]) {
                array_unshift($lcs, $a[$i - 1]);
                $i--;
                $j--;
            } elseif ($dp[$i - 1][$j] > $dp[$i][$j - 1]) {
                $i--;
            } else {
                $j--;
            }
        }

        return $lcs;
    }

    /**
     * 分词（中文按字符，英文按空格）
     */
    private static function tokenize(string $text): array
    {
        $tokens = [];
        $current = '';

        $chars = preg_split('//u', $text, -1, PREG_SPLIT_NO_EMPTY);
        foreach ($chars as $char) {
            if (preg_match('/[\x{4e00}-\x{9fff}\x{3000}-\x{303f}\x{ff00}-\x{ffef}]/u', $char)) {
                // 中文字符：单独作为一个 token
                if ($current !== '') {
                    $tokens[] = $current;
                    $current = '';
                }
                $tokens[] = $char;
            } elseif (preg_match('/\s/', $char)) {
                if ($current !== '') {
                    $tokens[] = $current;
                    $current = '';
                }
                $tokens[] = $char;
            } else {
                $current .= $char;
            }
        }
        if ($current !== '') {
            $tokens[] = $current;
        }

        return $tokens;
    }

    /**
     * 合并连续同类型 chunk
     */
    private static function mergeConsecutive(array $chunks): array
    {
        if (empty($chunks)) return [];

        $merged = [$chunks[0]];
        for ($i = 1; $i < count($chunks); $i++) {
            $last = &$merged[count($merged) - 1];
            if ($last['type'] === $chunks[$i]['type']) {
                $last['content'] .= ($last['type'] === 'equal' || empty($chunks[$i]['content']) ? '' : "\n") . $chunks[$i]['content'];
            } else {
                $merged[] = $chunks[$i];
            }
        }

        return $merged;
    }
}
