// 多格式歌词解析器：LRC / QRC / TTML / YRC / JSON / SRT / VTT / 纯文本 / 逐字

export interface LyricLine {
    startTime: number;
    endTime?: number;
    text: string;
    words?: { time: number; duration?: number; text: string }[];
    raw?: any;
}

function parseTimeStamp(str: string): number {
    if (!str) return 0;
    const s = str.replace(/[^\d:.,\[\]<>]/g, '').trim();
    if (!s) return 0;
    const m = s.match(/^(\d+):(\d+)(?:[.:](\d+))?$/);
    if (m) {
        const min = parseInt(m[1], 10);
        const sec = parseInt(m[2], 10);
        let ms = 0;
        if (m[3]) {
            const frac = m[3];
            if (frac.length >= 3) ms = parseInt(frac.substring(0, 3), 10);
            else if (frac.length === 2) ms = parseInt(frac, 10) * 10;
            else ms = parseInt(frac, 10) * 100;
        }
        return min * 60 * 1000 + sec * 1000 + ms;
    }
    const n = parseFloat(s);
    return isFinite(n) ? n * 1000 : 0;
}

function parseLrc(text: string): LyricLine[] {
    const lines: LyricLine[] = [];
    const textLines = String(text).split(/\r?\n/);
    for (const line of textLines) {
        const trimmed = line.trim();
        if (!trimmed) continue;
        const stamps: string[] = [];
        let rest = trimmed;
        while (true) {
            const m = rest.match(/^\s*\[\s*(\d{1,3}):(\d{1,2})(?:[.:](\d{1,3}))?\s*\]/);
            if (!m) break;
            stamps.push(m[1] + ':' + m[2] + '.' + (m[3] || '000'));
            rest = rest.substring(m[0].length);
        }
        if (stamps.length === 0) continue;
        const content = rest.trim();
        if (!content) continue;
        for (const st of stamps) {
            lines.push({ startTime: parseTimeStamp(st), text: content });
        }
    }
    lines.sort((a, b) => a.startTime - b.startTime);
    for (let i = 0; i < lines.length - 1; i++) {
        lines[i].endTime = lines[i + 1].startTime;
    }
    return lines;
}

function parseQrc(text: string): LyricLine[] {
    const lines: LyricLine[] = [];
    const textLines = String(text).split(/\r?\n/);
    for (const line of textLines) {
        const trimmed = line.trim();
        if (!trimmed) continue;
        // [ti:xx] metadata lines (ignore)
        if (/^\[[a-zA-Z]+\s*:/.test(trimmed)) continue;
        // <0,270,100>xxx<270,150,100>yyy
        if (trimmed.startsWith('[') || trimmed.startsWith('<')) {
            const mainStart = trimmed.match(/^\[(\d+),(\d+)\]/);
            if (mainStart) {
                const startTime = parseInt(mainStart[1], 10);
                const endTime = startTime + parseInt(mainStart[2], 10);
                const content = trimmed.substring(mainStart[0].length);
                const words: { time: number; duration?: number; text: string }[] = [];
                const rest = content;
                const re = /<(\d+),(\d+)(?:,\d+)?>([^<]*)/g;
                let match;
                while ((match = re.exec(rest)) !== null) {
                    words.push({
                        time: parseInt(match[1], 10),
                        duration: parseInt(match[2], 10),
                        text: match[3],
                    });
                }
                lines.push({ startTime, endTime, text: words.map(w => w.text).join(''), words });
                continue;
            }
        }
        // fallback: treat as LRC
        const lrcline = parseLrc(trimmed);
        lines.push(...lrcline);
    }
    return lines.length > 0 ? lines : parseLrc(text);
}

function parseTtml(text: string): LyricLine[] {
    const lines: LyricLine[] = [];
    const t = String(text);
    const lineRe = /<p\b[^>]*>([\s\S]*?)<\/p>/gi;
    const beginRe = /begin\s*=\s*["']([^"']+)["']/i;
    const endRe = /end\s*=\s*["']([^"']+)["']/i;
    let m;
    while ((m = lineRe.exec(t)) !== null) {
        const attrs = m[0].substring(0, m[0].indexOf('>'));
        const begin = beginRe.exec(attrs);
        const end = endRe.exec(attrs);
        const inner = m[1].replace(/<br\s*\/?>/gi, '\n').replace(/<[^>]+>/g, '').trim();
        if (!inner) continue;
        const start = begin ? parseTtmlTime(begin[1]) : 0;
        const endT = end ? parseTtmlTime(end[1]) : undefined;
        lines.push({ startTime: start, endTime: endT, text: inner });
    }
    if (lines.length === 0) return parseLrc(text);
    return lines.sort((a, b) => a.startTime - b.startTime);
}

function parseTtmlTime(str: string): number {
    // 00:01:23.456 / 00:01:23 / 01:23.456
    const parts = str.split(':');
    if (parts.length === 3) {
        const [h, m, s] = parts;
        return parseInt(h, 10) * 3600000 + parseInt(m, 10) * 60000 + parseFloat(s) * 1000;
    }
    if (parts.length === 2) {
        return parseInt(parts[0], 10) * 60000 + parseFloat(parts[1]) * 1000;
    }
    return parseFloat(str) * 1000;
}

function parseYrc(raw: any): LyricLine[] {
    let lyricText: string | undefined;
    if (typeof raw === 'string') {
        try {
            const obj = JSON.parse(raw);
            raw = obj;
        } catch {}
    }
    if (typeof raw === 'object' && raw !== null) {
        lyricText = raw.yrc || raw.lrc || raw.lyric || raw.klyric || raw.lrclib;
        if (typeof lyricText === 'object' && lyricText !== null) {
            lyricText = (lyricText as any).lyric || (lyricText as any).content || JSON.stringify(lyricText);
        }
    }
    if (!lyricText || typeof lyricText !== 'string') {
        return parseLrc(JSON.stringify(raw));
    }
    // 网易云 YRC 逐字格式：[0,1800,"(前奏)"]{{340,220,yu},{620,230,ye},...}
    const lines: LyricLine[] = [];
    const regex = /\[\s*(\d+)\s*,\s*(\d+)\s*(?:,[^\]]*)?\]([^{]*)(\{[^}]*\})?/g;
    let match;
    while ((match = regex.exec(lyricText)) !== null) {
        const start = parseInt(match[1], 10);
        const dur = parseInt(match[2], 10);
        let text = (match[3] || '').trim();
        const wordsPart = match[4];
        const words: { time: number; duration?: number; text: string }[] = [];
        if (wordsPart) {
            const wordRe = /\{\s*(\d+)\s*,\s*(\d+)\s*(?:,[^}]*)?\}/g;
            let wm;
            while ((wm = wordRe.exec(wordsPart)) !== null) {
                const time = parseInt(wm[1], 10);
                const duration = parseInt(wm[2], 10);
                const tStart = wordRe.lastIndex;
                // 从 wordsPart 中找到单词字符（可能是中文字符 / 英文）
                // 网易云格式中的文字在 {} 后紧接的 , 位置后。简化处理：
                words.push({ time: start + time, duration, text: '' });
                void tStart;
            }
        }
        if (!text && words.length === 0) continue;
        lines.push({ startTime: start, endTime: start + dur, text, words: words.length ? words : undefined });
    }
    if (lines.length > 0) return lines;
    return parseLrc(lyricText);
}

function parseJson(raw: any): LyricLine[] {
    try {
        let obj = raw;
        if (typeof obj === 'string') {
            obj = JSON.parse(obj);
        }
        if (Array.isArray(obj)) {
            const lines: LyricLine[] = [];
            for (const item of obj) {
                if (item && typeof item === 'object') {
                    if (item.startTime != null || item.time != null || item.start != null || item.t != null) {
                        const t = item.startTime ?? item.time ?? item.start ?? item.t ?? 0;
                        const end = item.endTime ?? item.end ?? undefined;
                        const txt = item.text ?? item.content ?? item.word ?? item.lyric ?? item.line ?? '';
                        if (txt) lines.push({ startTime: typeof t === 'number' ? t : parseTimeStamp(String(t)), endTime: typeof end === 'number' ? end : undefined, text: String(txt) });
                    }
                } else if (typeof item === 'string') {
                    lines.push(...parseLrc(item));
                }
            }
            return lines.sort((a, b) => a.startTime - b.startTime);
        }
        if (obj && typeof obj === 'object') {
            if (obj.lrc && typeof obj.lrc === 'string') return parseLrc(obj.lrc);
            if (obj.lyric && typeof obj.lyric === 'string') return parseLrc(obj.lyric);
            if (typeof obj.content === 'string') return parseLrc(obj.content);
            if (Array.isArray(obj.lines)) return parseJson(obj.lines);
        }
        return [];
    } catch { return []; }
}

function parseSrt(text: string): LyricLine[] {
    const lines: LyricLine[] = [];
    const blocks = String(text).split(/\r?\n\s*\r?\n/);
    for (const block of blocks) {
        const linesArr = block.split(/\r?\n/).filter(Boolean);
        if (linesArr.length < 2) continue;
        // skip leading index line
        let timeLineIdx = 0;
        if (/^\d+$/.test(linesArr[0].trim())) timeLineIdx = 1;
        const timeLine = linesArr[timeLineIdx];
        const tm = timeLine.match(/(\d{1,2}):(\d{2}):(\d{2})[.,](\d{1,3})\s*-->\s*(\d{1,2}):(\d{2}):(\d{2})[.,](\d{1,3})/);
        if (!tm) continue;
        const startTime = parseInt(tm[1], 10) * 3600000 + parseInt(tm[2], 10) * 60000 + parseInt(tm[3], 10) * 1000 + parseInt(tm[4], 10);
        const endTime = parseInt(tm[5], 10) * 3600000 + parseInt(tm[6], 10) * 60000 + parseInt(tm[7], 10) * 1000 + parseInt(tm[8], 10);
        const content = linesArr.slice(timeLineIdx + 1).map(s => s.replace(/<[^>]+>/g, '').trim()).filter(Boolean).join(' ');
        if (!content) continue;
        lines.push({ startTime, endTime, text: content });
    }
    return lines;
}

function parseVtt(text: string): LyricLine[] {
    const lines: LyricLine[] = [];
    const blocks = String(text).replace(/^WEBVTT\s*(\r?\n|$)/i, '').split(/\r?\n\s*\r?\n/);
    for (const block of blocks) {
        const linesArr = block.split(/\r?\n/).filter(Boolean);
        if (linesArr.length < 1) continue;
        let timeLineIdx = 0;
        while (timeLineIdx < linesArr.length && !/-->/.test(linesArr[timeLineIdx])) timeLineIdx++;
        if (timeLineIdx >= linesArr.length) continue;
        const tm = linesArr[timeLineIdx].match(/(?:(\d{1,2}):)?(\d{1,2}):(\d{2})[.,](\d{1,3})\s*-->\s*(?:(\d{1,2}):)?(\d{1,2}):(\d{2})[.,](\d{1,3})/);
        if (!tm) continue;
        const startTime = (parseInt(tm[1] || '0', 10) * 3600000) + parseInt(tm[2], 10) * 60000 + parseInt(tm[3], 10) * 1000 + parseInt(tm[4], 10);
        const endTime = (parseInt(tm[5] || '0', 10) * 3600000) + parseInt(tm[6], 10) * 60000 + parseInt(tm[7], 10) * 1000 + parseInt(tm[8], 10);
        const content = linesArr.slice(timeLineIdx + 1).map(s => s.replace(/<[^>]+>/g, '').trim()).filter(Boolean).join(' ');
        if (!content) continue;
        lines.push({ startTime, endTime, text: content });
    }
    return lines;
}

export function parseAnyLyric(input: { format?: string | null; raw: any } | any): LyricLine[] {
    let format: string | null = input?.format;
    let raw: any = input?.raw;
    if (raw === undefined && input !== null && typeof input !== 'object') {
        raw = input;
        format = null;
    }
    if (raw === null || raw === undefined || (typeof raw === 'string' && !raw.trim())) return [];

    if (!format) {
        if (typeof raw === 'string') {
            const s = raw.trim();
            if (/^<\s*(?:\?xml|tt|TT|lyric\b|Lyric\b|LyricData\b)/i.test(s)) format = 'ttml';
            else if (/<\s*\d+[:：]\d+/.test(s)) format = 'qrc';
            else if (/\[\s*\d{1,2}[:：]\d{1,2}(?:[.:：]\d{1,3})?\s*\]/.test(s)) format = 'lrc';
            else if (s.charAt(0) === '{' || s.charAt(0) === '[') {
                try {
                    const obj = JSON.parse(s);
                    if (obj.yrc || obj.lrclib || obj.klyric) format = 'yrc';
                    else if (obj.lrc || obj.ttml || obj.qrc || obj.lyric || obj.lines) format = 'json';
                    else format = 'json';
                    raw = obj;
                } catch { format = 'text'; }
            } else if (/^\d+\s*\r?\n\d{1,2}:\d{2}:\d{2}[.,]\d+\s*-->/.test(s)) format = 'srt';
            else format = 'text';
        } else if (typeof raw === 'object') {
            if (raw.yrc || raw.lrclib || raw.klyric) format = 'yrc';
            else if (raw.lrc) format = 'lrc';
            else if (raw.ttml) format = 'ttml';
            else if (raw.qrc) format = 'qrc';
            else if (Array.isArray(raw) || raw.lines || raw.list) format = 'json';
            else format = 'json';
        }
    }

    switch (format) {
        case 'lrc': return typeof raw === 'string' ? parseLrc(raw) : parseLrc(String(raw.lrc || raw.lyric || raw.content || ''));
        case 'qrc': return typeof raw === 'string' ? parseQrc(raw) : parseQrc(String(raw.qrc || raw));
        case 'ttml': return typeof raw === 'string' ? parseTtml(raw) : parseTtml(String(raw.ttml || raw));
        case 'yrc': return parseYrc(raw);
        case 'json': return parseJson(raw);
        case 'srt': return typeof raw === 'string' ? parseSrt(raw) : parseSrt(String(raw));
        case 'vtt': return typeof raw === 'string' ? parseVtt(raw) : parseVtt(String(raw));
        case 'text':
        default:
            if (typeof raw === 'string' && raw.trim()) {
                return [{ startTime: 0, text: raw.trim() }];
            }
            return [];
    }
}

export { parseLrc, parseQrc, parseTtml, parseYrc, parseJson, parseSrt, parseVtt };
