import { extractYoutubePlayerMetadata, fetchYoutubePlayerMetadata, } from "../../transcript/providers/youtube/captions.js";
import { fetchMediaMetadataWithYtDlp } from "../../transcript/providers/youtube/yt-dlp.js";
import { extractYouTubeVideoId } from "../../url.js";
import { fetchWithTimeout } from "../fetch-with-timeout.js";
const SOURCE_METRICS_TTL_MS = 60 * 60 * 1_000;
const METRICS_BEST_EFFORT_TIMEOUT_MS = 5_000;
const YOUTUBE_TRANSCRIPT_SOURCES = new Set([
    "youtubei",
    "captionTracks",
    "youtube-media",
    "apify",
    "yt-dlp",
]);
function readSourceMetrics(value) {
    if (!value || typeof value !== "object" || Array.isArray(value))
        return null;
    const record = value;
    if (record.platform !== "youtube" ||
        typeof record.videoId !== "string" ||
        record.videoId.length === 0 ||
        (record.viewCount !== null &&
            (typeof record.viewCount !== "number" ||
                !Number.isSafeInteger(record.viewCount) ||
                record.viewCount < 0)) ||
        typeof record.observedAt !== "string" ||
        !Number.isFinite(Date.parse(record.observedAt))) {
        return null;
    }
    return {
        platform: "youtube",
        videoId: record.videoId,
        viewCount: record.viewCount,
        observedAt: record.observedAt,
    };
}
export async function refreshYoutubeSourceMetrics({ url, html, detectedVideo, transcriptResolution, deps, timeoutMs, startedAtMs, }) {
    const metricsDeadlineMs = startedAtMs + Math.min(timeoutMs, METRICS_BEST_EFFORT_TIMEOUT_MS);
    const remainingMetricsMs = () => Math.max(0, metricsDeadlineMs - Date.now());
    const existingMetrics = readSourceMetrics(transcriptResolution.metadata?.sourceMetrics);
    const existingVideoId = existingMetrics?.videoId ?? null;
    const urlVideoId = extractYouTubeVideoId(url);
    const detectedVideoId = detectedVideo?.kind === "youtube" ? extractYouTubeVideoId(detectedVideo.url) : null;
    const resolvedEmbedId = detectedVideoId &&
        transcriptResolution.text &&
        transcriptResolution.source &&
        YOUTUBE_TRANSCRIPT_SOURCES.has(transcriptResolution.source)
        ? detectedVideoId
        : null;
    const resolvedVideoId = urlVideoId ?? existingVideoId ?? resolvedEmbedId;
    if (!resolvedVideoId)
        return;
    const htmlPlayerMetadata = urlVideoId ? extractYoutubePlayerMetadata(html) : null;
    const existingMetricsForVideo = existingMetrics?.videoId === resolvedVideoId ? existingMetrics : null;
    let sourceMetrics = htmlPlayerMetadata && urlVideoId && htmlPlayerMetadata.viewCount !== null
        ? {
            platform: "youtube",
            videoId: urlVideoId,
            viewCount: htmlPlayerMetadata.viewCount,
            observedAt: new Date(startedAtMs).toISOString(),
        }
        : (existingMetricsForVideo ??
            (htmlPlayerMetadata && urlVideoId
                ? {
                    platform: "youtube",
                    videoId: urlVideoId,
                    viewCount: null,
                    observedAt: new Date(startedAtMs).toISOString(),
                }
                : null));
    const existingObservedTime = existingMetricsForVideo
        ? Date.parse(existingMetricsForVideo.observedAt)
        : Number.NaN;
    const existingMetricsAreFresh = existingMetricsForVideo !== null &&
        Number.isFinite(existingObservedTime) &&
        Date.now() - existingObservedTime < SOURCE_METRICS_TTL_MS;
    const htmlHasViewCount = typeof htmlPlayerMetadata?.viewCount === "number";
    const shouldRefresh = !htmlHasViewCount && !existingMetricsAreFresh;
    const remainingTimeoutMs = remainingMetricsMs();
    if (shouldRefresh && remainingTimeoutMs > 0) {
        if (!urlVideoId) {
            const refreshedMetrics = await fetchYoutubeSourceMetrics({
                fetchImpl: deps.fetch,
                ytDlpPath: deps.ytDlpPath,
                videoId: resolvedVideoId,
                timeoutMs: remainingTimeoutMs,
            });
            if (refreshedMetrics) {
                sourceMetrics = refreshedMetrics;
            }
        }
        else {
            const refreshed = await fetchYoutubePlayerMetadata(deps.fetch, {
                html,
                videoId: resolvedVideoId,
                timeoutMs: remainingTimeoutMs,
            });
            let refreshedViewCount = refreshed?.viewCount ?? null;
            let refreshObserved = refreshed !== null;
            const ytDlpTimeoutMs = remainingMetricsMs();
            if (refreshedViewCount === null && deps.ytDlpPath && ytDlpTimeoutMs > 0) {
                const ytDlpMetadata = await fetchMediaMetadataWithYtDlp({
                    ytDlpPath: deps.ytDlpPath,
                    url: `https://www.youtube.com/watch?v=${resolvedVideoId}`,
                    timeoutMs: ytDlpTimeoutMs,
                });
                refreshedViewCount = ytDlpMetadata?.viewCount ?? null;
                refreshObserved ||= ytDlpMetadata !== null;
            }
            if (refreshObserved) {
                sourceMetrics = {
                    platform: "youtube",
                    videoId: resolvedVideoId,
                    viewCount: refreshedViewCount,
                    observedAt: new Date().toISOString(),
                };
            }
        }
    }
    if (!sourceMetrics)
        return;
    transcriptResolution.metadata = {
        ...(transcriptResolution.metadata ?? {}),
        sourceMetrics,
    };
}
export async function fetchYoutubeSourceMetrics({ fetchImpl, ytDlpPath, videoId, timeoutMs, }) {
    const startedAt = Date.now();
    const watchUrl = `https://www.youtube.com/watch?v=${videoId}`;
    let html = "";
    try {
        html = await fetchWithTimeout(fetchImpl, watchUrl, {
            headers: {
                "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0 Safari/537.36",
                Accept: "text/html,application/xhtml+xml",
            },
        }, timeoutMs, async (response) => (response.ok ? await response.text() : ""));
    }
    catch {
        // yt-dlp remains available as an independent metadata fallback.
    }
    const htmlPlayerMetadata = html ? extractYoutubePlayerMetadata(html) : null;
    const htmlObservation = htmlPlayerMetadata
        ? {
            platform: "youtube",
            videoId,
            viewCount: htmlPlayerMetadata.viewCount,
            observedAt: new Date(startedAt).toISOString(),
        }
        : null;
    if (htmlObservation && htmlObservation.viewCount !== null)
        return htmlObservation;
    const remainingAfterHtml = Math.max(0, timeoutMs - (Date.now() - startedAt));
    let playerObservation = null;
    if (html && remainingAfterHtml > 0) {
        const player = await fetchYoutubePlayerMetadata(fetchImpl, {
            html,
            videoId,
            timeoutMs: remainingAfterHtml,
        });
        if (player) {
            playerObservation = {
                platform: "youtube",
                videoId,
                viewCount: player.viewCount,
                observedAt: new Date().toISOString(),
            };
            if (player.viewCount !== null)
                return playerObservation;
        }
    }
    const remainingAfterPlayer = Math.max(0, timeoutMs - (Date.now() - startedAt));
    if (ytDlpPath && remainingAfterPlayer > 0) {
        const metadata = await fetchMediaMetadataWithYtDlp({
            ytDlpPath,
            url: watchUrl,
            timeoutMs: remainingAfterPlayer,
        });
        if (metadata) {
            return {
                platform: "youtube",
                videoId,
                viewCount: metadata.viewCount,
                observedAt: new Date().toISOString(),
            };
        }
    }
    return playerObservation ?? htmlObservation;
}
//# sourceMappingURL=youtube-source-metrics.js.map