parseCodecs = (format) => {
    const mimeType = format["mimeType"]
    if (!mimeType) {
        return {};
    }
    const regex = /(?<mimetype>[^/]+\/[^;]+)(?:;\s*codecs="?(?<codecs>[^"]+))?/;
    const match = mimeType.match(regex);
    if (!match) {
        return {};
    }
    const codecs = match.groups.codecs;
    if (!codecs) {
        return {};
    }

    const splitCodecs = codecs.trim().replace(/,$/, '').split(',').map(str => str.trim()).filter(Boolean);
    let vcodec = null;
    let acodec = null;

    for (const fullCodec of splitCodecs) {
        const codec = fullCodec.split('.')[0];

        if (['avc1', 'avc2', 'avc3', 'avc4', 'vp9', 'vp8', 'hev1', 'hev2', 'h263', 'h264', 'mp4v', 'hvc1', 'av01', 'theora'].includes(codec)) {
            if (!vcodec) {
                vcodec = fullCodec;
            }
        } else if (['mp4a', 'opus', 'vorbis', 'mp3', 'aac', 'ac-3', 'ec-3', 'eac3', 'dtsc', 'dtse', 'dtsh', 'dtsl'].includes(codec)) {
            if (!acodec) {
                acodec = fullCodec;
            }
        } else {
            console.warn(`WARNING: Unknown codec ${fullCodec}`);
        }
    }

    if (!vcodec && !acodec) {
        if (splitCodecs.length === 2) {
            return {
                vcodec: splitCodecs[0],
                acodec: splitCodecs[1]
            };
        }
    } else {
        return {
            vcodec: vcodec,
            acodec: acodec
        };
    }

    return {};
}

getStringBetween = (string, needleStart, needleEnd, offsetStart = 0, offsetEnd = 0) => {
    const x = string.indexOf(needleStart);
    const y = needleEnd ? string.indexOf(needleEnd, x) : string.length;
    return string.substring(x + needleStart.length + offsetEnd, y + offsetStart);
}

getUrlFromSignature = (signatureCipher, baseContent) => {
    const decipher = getDecipherFunction(baseContent);
    const searchParams = {}
    for (const item of signatureCipher.split("&")) {
        const [key, value] = item.split('=');
        searchParams[decodeURIComponent(key)] = decodeURIComponent(value);
    }
    const [url, signature, sp] = [searchParams["url"], searchParams["s"], searchParams["sp"]];
    console.log(signatureCipher, url, signature, sp);
    return `${url}&${sp}=${decipher(signature)}`;
}

getDecipherFunction = (string) => {
    const js = string.replace("var _yt_player={}", "");
    const top = getStringBetween(js, `a=a.split("")`, "};", 1, -28);
    const beginningOfFunction =
        "var " + getStringBetween(top, `a=a.split("")`, "(", 10, 1).split(".")[0] + "=";
    const side = getStringBetween(js, beginningOfFunction, "};", 2, -beginningOfFunction.length);
    console.log(side + top);
    return eval(side + top);
};

request = async (method, url, data = null, headers = {}, local) => {
    if (local) {
        url = url.replace("https://www.youtube.com", "http://127.0.0.1");
    }
    console.log(`请求url:${url}`)
    console.log(`请求data:${data}`)
    console.log(`请求method:${method}`)
    console.log(`请求headers:${JSON.stringify((headers))}`)
    if (local) {
        return fetch(url, {
            "method": method,
            "headers": headers,
            "body": data,
        }).then(res => res.text())
    }
    return new Promise((resolve, reject) => {
        AF.request(url, method, data, headers, (data, err) => {
            if (err) {
                console.log(`请求失败: ${err}`)
                reject(err);
            } else {
                resolve(data);
            }
        });
    })
}

detail = async (url, local) => {
    try {
        console.log(`接受到解析请求: ${url}`);
        const headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.101 Safari/537.36',
        }
        const html = await request('GET', url, null, headers, local);

        let baseJsUrl = `https://www.youtube.com${JSON.parse(html.match(/set\(({.+?})\);/)[1])["PLAYER_JS_URL"]}`
        console.log(`解析baseUrl: ${baseJsUrl}`);

        const baseContent = await request('GET', baseJsUrl, null, headers, local);

        let regex = /var ytInitialPlayerResponse\s*=\s*({.*?});/;
        let match = html.match(regex);
        if (!match || !match.length) {
            console.log("解释失败: 无法找到json");
            throw new Error('JSON not found.');
        }
        const ytInitialPlayerResponse = JSON.parse(match[1]);
        console.log(`ytInitialPlayerResponse: ${JSON.stringify(ytInitialPlayerResponse)}`)
        const originVideoDetails = ytInitialPlayerResponse["videoDetails"];
        const thumbnails = []
        for (const item of originVideoDetails["thumbnail"]["thumbnails"]) {
            thumbnails.push({
                "url": item["url"],
                "width": item["width"] + "",
                "height": item["height"] + ""
            })
        }

        const formats = []
        for (let format of [].concat(ytInitialPlayerResponse["streamingData"]["formats"]).concat(ytInitialPlayerResponse["streamingData"]["adaptiveFormats"])) {
            console.log(`format: ${JSON.stringify(format)}`);
            if (!format["url"]) {
                format["url"] = getUrlFromSignature(format["signatureCipher"], baseContent);
            }
            if (format["url"]) {
                const {vcodec, acodec} = parseCodecs(format)
                if (vcodec && acodec) {
                    formats.push({
                        "width": format["width"] + "",
                        "height": format["height"] + "",
                        "type": format["mimeType"],
                        "quality": format["quality"],
                        "itag": format["itag"],
                        "fps": format["fps"] + "",
                        "bitrate": format["bitrate"] + "",
                        "url": format["url"],
                        "ext": "mp4",
                        "vcodec": vcodec,
                        "acodec": acodec,
                        "vbr": "0",
                        "abr": "0",
                        "container": "mp4_dash"
                    })
                }
            }
        }

        regex = /var ytInitialData\s*=\s*({.*?});/;
        match = html.match(regex);
        if (!match || !match.length) {
            console.log(`解析失败，无法找到 ytInitialData`);
            throw new Error('JSON not found.');
        }
        const ytInitialData = JSON.parse(match[1]);
        console.log(`ytInitialData: ${JSON.stringify(ytInitialData)}`);
        const recommendInfo = [];
        for (const item of ytInitialData["contents"]["twoColumnWatchNextResults"]["secondaryResults"]["secondaryResults"]["results"]) {
            if (item["compactVideoRenderer"]) {
                const recommendVideo = item["compactVideoRenderer"];
                console.log(`推荐视频: ${JSON.stringify(recommendVideo)}`);
                if (recommendVideo["videoId"]) {
                    recommendInfo.push({
                        "type": "gridVideoRenderer",
                        "videoId": recommendVideo["videoId"],
                        "title": recommendVideo["title"]?.["simpleText"],
                        "thumbnails": recommendVideo["thumbnail"]?.["thumbnails"],
                        "channelName": recommendVideo["longBylineText"]?.["runs"]?.[0]?.["text"],
                        "publishedTimeText": recommendVideo["publishedTimeText"]?.["simpleText"],
                        "viewCountText": recommendVideo["viewCountText"]?.["simpleText"],
                        "shortViewCountText": recommendVideo["shortViewCountText"]?.["simpleText"],
                        "lengthText": recommendVideo["lengthText"]?.["simpleText"]
                    })
                }
            }
        }

        const videoDetails = {
            "isLiveContent": originVideoDetails["isLiveContent"],
            "title": originVideoDetails["title"],
            "thumbnails": thumbnails,
            "description": originVideoDetails["shortDescription"],
            "lengthSeconds": originVideoDetails["lengthSeconds"],
            "viewCount": originVideoDetails["viewCount"],
            "keywords": originVideoDetails["keywords"],
            "author": originVideoDetails["author"],
            "channelID": originVideoDetails["channelId"],
            "recommendInfo": recommendInfo,
            "channelURL": `https://www.youtube.com/channel/${originVideoDetails["channelId"]}`,
            "videoId": originVideoDetails["videoId"]
        }
        const ret = {
            "code": 200,
            "msg": "",
            "data": {
                "videoDetails": videoDetails,
                "streamingData": {
                    "formats": formats
                }
            },
            "id": "MusicDetailViewModel_detail_url"
        }
        console.log(`解析结果: ${JSON.stringify(ret)}`);
        return ret;
    } catch (e) {
        const ret = {
            "code": -1,
            "msg": e.toString()
        }
        console.log(`解析失败: ${JSON.stringify(ret)}`);
        return ret
    }
}

search = async (keyword, next, local) => {
    try {
        console.log(`接受到搜索请求: ${keyword}`);
        console.log(`next: ${next}`);
        if (next) {
            const nextObject = JSON.parse(next);
            const key = nextObject["key"];
            const body = {
                context: {
                    client: {
                        clientName: "WEB",
                        clientVersion: "2.20240506.01.00",
                    },
                },
                continuation: nextObject["continuation"]
            };
            let res = await request('POST', `https://www.youtube.com/youtubei/v1/search?key=${key}`, JSON.stringify(body), {}, local);
            res = JSON.parse(res);
            console.log(`搜索结果res: ${JSON.stringify(res)}`)
            const videos = [];
            for (const item of res["onResponseReceivedCommands"][0]["appendContinuationItemsAction"]["continuationItems"][0]["itemSectionRenderer"]["contents"]) {
                const video = item["videoRenderer"];
                if (video && video["videoId"]) {
                    videos.push({
                        "type": "videoWithContextRenderer",
                        "data": {
                            "videoId": video["videoId"],
                            "title": video["title"]?.["runs"]?.[0]?.["text"],
                            "thumbnails": video["thumbnail"]?.["thumbnails"],
                            "channelName": video["longBylineText"]?.["runs"]?.[0]?.["text"],
                            "publishedTimeText": video["publishedTimeText"]?.["simpleText"],
                            "viewCountText": video["viewCountText"]?.["simpleText"],
                            "shortViewCountText": video["shortViewCountText"]?.["simpleText"],
                            "lengthText": video["lengthText"]?.["simpleText"]
                        }
                    });
                }
            }
            const ret = {
                "code": 200,
                "msg": "",
                "data": {
                    "data": videos,
                    "next": JSON.stringify({
                        "key": nextObject["key"],
                        "continuation": res["onResponseReceivedCommands"][0]["appendContinuationItemsAction"]["continuationItems"][1]["continuationItemRenderer"]["continuationEndpoint"]["continuationCommand"]["token"],
                    }),
                },
                "id": "MusicSearchResultViewModel_search_result"
            }
            console.log(`搜索结果成功1: ${JSON.stringify(ret)}`);
            return ret;
        } else {
            let url = `https://www.youtube.com/results?q=${encodeURIComponent(keyword)}&sp=EgIQAQ%253D%253D`;

            const html = await request('GET', url, null, {}, local);

            let regex = /var ytInitialData\s*=\s*({.*?});/;
            let match = html.match(regex);
            if (!match || !match.length) {
                console.log("搜索失败：无法找到ytInitialData");
                throw new Error('JSON not found.');
            }

            const ytInitialDataResp = JSON.parse(match[1]);
            console.log(`ytInitialDataResp: ${JSON.stringify(ytInitialDataResp)}`);
            const videos = [];
            for (const item of ytInitialDataResp["contents"]["twoColumnSearchResultsRenderer"]["primaryContents"]["sectionListRenderer"]["contents"][0]["itemSectionRenderer"]["contents"]) {
                if (item["videoRenderer"]) {
                    const video = item["videoRenderer"];
                    if (video && video["videoId"]) {
                        videos.push({
                            "type": "videoWithContextRenderer",
                            "data": {
                                "videoId": video["videoId"],
                                "title": video["title"]?.["runs"]?.[0]?.["text"],
                                "thumbnails": video["thumbnail"]?.["thumbnails"],
                                "channelName": video["longBylineText"]?.["runs"]?.[0]?.["text"],
                                "publishedTimeText": video["publishedTimeText"]?.["simpleText"],
                                "viewCountText": video["viewCountText"]?.["simpleText"],
                                "shortViewCountText": video["shortViewCountText"]?.["simpleText"],
                                "lengthText": video["lengthText"]?.["simpleText"]
                            }
                        });
                    }
                }
            }

            let next = {};
            if (html.split("innertubeApiKey").length > 0) {
                // 写入path
                next["key"] = html
                    .split("innertubeApiKey")[1]
                    .trim()
                    .split(",")[0]
                    .split('"')[2];
            }
            next["continuation"] = ytInitialDataResp["contents"]["twoColumnSearchResultsRenderer"]["primaryContents"]["sectionListRenderer"]["contents"][1]["continuationItemRenderer"]["continuationEndpoint"]["continuationCommand"]["token"]

            const ret = {
                "code": 200,
                "msg": "",
                "data": {
                    "data": videos,
                    "next": JSON.stringify(next),
                },
                "id": "MusicSearchResultViewModel_search_result"
            }
            console.log(`搜索结果成功2: ${JSON.stringify(ret)}`);
            return ret;
        }
    } catch (e) {
        const ret = {
            "code": -1,
            "msg": e.toString()
        }
        console.log(`解析失败: ${JSON.stringify(ret)}`);
        return ret;
    }
}
