import { withBunCompressionHeaders } from "../../../bun.js";
import { fetchWithTimeout } from "../../../link-preview/fetch-with-timeout.js";
import { extractYoutubeiBootstrap } from "./api.js";
import { INNERTUBE_API_KEY_REGEX, REQUEST_HEADERS, YT_INITIAL_PLAYER_RESPONSE_TOKEN, isObjectLike, } from "./captions-shared.js";
function extractBalancedJsonObject(source, startAt) {
    const start = source.indexOf("{", startAt);
    if (start < 0) {
        return null;
    }
    let depth = 0;
    let inString = false;
    let quote = null;
    let escaping = false;
    for (let i = start; i < source.length; i += 1) {
        const ch = source[i];
        if (!ch)
            continue;
        if (inString) {
            if (escaping) {
                escaping = false;
                continue;
            }
            if (ch === "\\") {
                escaping = true;
                continue;
            }
            if (quote && ch === quote) {
                inString = false;
                quote = null;
            }
            continue;
        }
        if (ch === '"' || ch === "'") {
            inString = true;
            quote = ch;
            continue;
        }
        if (ch === "{") {
            depth += 1;
            continue;
        }
        if (ch === "}") {
            depth -= 1;
            if (depth === 0) {
                return source.slice(start, i + 1);
            }
        }
    }
    return null;
}
export function extractInitialPlayerResponse(html) {
    const tokenIndex = html.indexOf(YT_INITIAL_PLAYER_RESPONSE_TOKEN);
    if (tokenIndex < 0) {
        return null;
    }
    const assignmentIndex = html.indexOf("=", tokenIndex);
    if (assignmentIndex < 0) {
        return null;
    }
    const objectText = extractBalancedJsonObject(html, assignmentIndex);
    if (!objectText) {
        return null;
    }
    try {
        const parsed = JSON.parse(objectText);
        return isObjectLike(parsed) ? parsed : null;
    }
    catch {
        return null;
    }
}
function coerceDurationSeconds(value) {
    const asNumber = typeof value === "number" ? value : typeof value === "string" ? Number(value) : Number.NaN;
    if (!Number.isFinite(asNumber) || asNumber <= 0)
        return null;
    return asNumber;
}
function coerceCount(value) {
    const asNumber = typeof value === "number" ? value : typeof value === "string" ? Number(value) : Number.NaN;
    if (!Number.isSafeInteger(asNumber) || asNumber < 0)
        return null;
    return asNumber;
}
function extractDurationSecondsFromHtml(html) {
    const candidates = [
        /"lengthSeconds":"(\d+)"/,
        /"lengthSeconds":(\d+)/,
        /"durationSeconds":"(\d+)"/,
        /"durationSeconds":(\d+)/,
    ];
    for (const pattern of candidates) {
        const match = html.match(pattern);
        if (!match?.[1])
            continue;
        const value = Number(match[1]);
        if (Number.isFinite(value) && value > 0)
            return value;
    }
    return null;
}
function extractPlayerMetadata(payload) {
    return {
        durationSeconds: extractDurationSecondsFromPlayerPayload(payload),
        viewCount: extractViewCountFromPlayerPayload(payload),
    };
}
export function extractDurationSecondsFromPlayerPayload(payload) {
    const videoDetails = payload.videoDetails;
    if (isObjectLike(videoDetails)) {
        const duration = coerceDurationSeconds(videoDetails.lengthSeconds);
        if (duration)
            return duration;
    }
    const microformat = payload.microformat;
    if (isObjectLike(microformat)) {
        const renderer = microformat.playerMicroformatRenderer;
        if (isObjectLike(renderer)) {
            const duration = coerceDurationSeconds(renderer.lengthSeconds);
            if (duration)
                return duration;
        }
    }
    return null;
}
export function extractViewCountFromPlayerPayload(payload) {
    const videoDetails = payload.videoDetails;
    if (isObjectLike(videoDetails)) {
        const viewCount = coerceCount(videoDetails.viewCount);
        if (viewCount !== null)
            return viewCount;
    }
    const microformat = payload.microformat;
    if (isObjectLike(microformat)) {
        const renderer = microformat.playerMicroformatRenderer;
        if (isObjectLike(renderer)) {
            const viewCount = coerceCount(renderer.viewCount);
            if (viewCount !== null)
                return viewCount;
        }
    }
    return null;
}
export function extractYoutubeDurationSeconds(html) {
    return (extractYoutubePlayerMetadata(html)?.durationSeconds ?? extractDurationSecondsFromHtml(html));
}
export function extractYoutubePlayerMetadata(html) {
    const playerResponse = extractInitialPlayerResponse(html);
    return playerResponse ? extractPlayerMetadata(playerResponse) : null;
}
export function extractYoutubeViewCount(html) {
    return extractYoutubePlayerMetadata(html)?.viewCount ?? null;
}
export function extractInnertubeApiKey(html) {
    const match = html.match(INNERTUBE_API_KEY_REGEX);
    const key = match?.[1] ?? match?.[2] ?? null;
    return typeof key === "string" && key.trim().length > 0 ? key.trim() : null;
}
export async function fetchYoutubePlayerPayload(fetchImpl, { html, videoId, timeoutMs }) {
    const bootstrap = extractYoutubeiBootstrap(html);
    const apiKey = bootstrap?.apiKey ?? extractInnertubeApiKey(html);
    if (!apiKey)
        return null;
    const context = bootstrap?.context;
    const clientContext = isObjectLike(context) && isObjectLike(context.client) ? context.client : null;
    const requestBody = {
        context: clientContext && isObjectLike(context)
            ? context
            : {
                client: {
                    clientName: "ANDROID",
                    clientVersion: "20.10.38",
                },
            },
        videoId,
    };
    try {
        const userAgent = REQUEST_HEADERS["User-Agent"] ??
            "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36";
        return await fetchWithTimeout(fetchImpl, `https://www.youtube.com/youtubei/v1/player?key=${apiKey}`, {
            method: "POST",
            headers: withBunCompressionHeaders({
                "Content-Type": "application/json",
                "User-Agent": userAgent,
                "Accept-Language": REQUEST_HEADERS["Accept-Language"] ?? "en-US,en;q=0.9",
                Accept: "application/json",
            }),
            body: JSON.stringify(requestBody),
        }, timeoutMs, async (response) => {
            if (!response.ok)
                return null;
            const parsed = await response.json();
            return isObjectLike(parsed) ? parsed : null;
        });
    }
    catch {
        return null;
    }
}
export async function fetchYoutubeDurationSecondsViaPlayer(fetchImpl, { html, videoId }) {
    const payload = await fetchYoutubePlayerPayload(fetchImpl, { html, videoId });
    if (!payload)
        return null;
    return extractDurationSecondsFromPlayerPayload(payload);
}
export async function fetchYoutubePlayerMetadata(fetchImpl, { html, videoId, timeoutMs }) {
    const payload = await fetchYoutubePlayerPayload(fetchImpl, { html, videoId, timeoutMs });
    return payload ? extractPlayerMetadata(payload) : null;
}
//# sourceMappingURL=captions-player.js.map