import LibAVFactory from '../lib/libav-6.5.7.1-h264-aac-mp3.wasm.mjs';

console.log("[Worker] Initialized hls_worker.js");

class HlsDownloader {
    constructor() {
        this.libav = null;
        this.opfsWriter = null;
    }

    async init() {
        if (this.libav) return;
        this.libav = await LibAVFactory({
            wasmurl: '../lib/libav-6.5.7.1-h264-aac-mp3.wasm.wasm'
        });
    }

    async processFullDownload(videoId, visitorData, hlsManifestUrl, progressCallback, providedVideoUrl, providedAudioUrl, duration, isAudioOnly = false, mp3Bitrate = 0, videoTitle = '') {
        let videoUrl = providedVideoUrl;
        let audioUrl = providedAudioUrl;

        // If specific URLs are provided, skip manifest parsing
        if (videoUrl || (isAudioOnly && audioUrl)) {
            console.log("[HLS] Using provided stream URLs directly.");
            if (!audioUrl) audioUrl = videoUrl; // Fallback if not separate
        }
        else if (hlsManifestUrl) {
            console.log("[HLS] Using provided Manifest URL, parsing streams.");
            // Fetch the manifest content directly
            const masterText = await (await fetch(hlsManifestUrl)).text();

            // Parse 720p (Video)
            // Prioritize standard resolution tag
            const vidMatch = masterText.match(/#EXT-X-STREAM-INF:.*RESOLUTION=1280x720[^\n]*\n(https:\/\/[^\s]+)/);
            videoUrl = vidMatch ? vidMatch[1] : null;

            if (!videoUrl) {
                console.warn("[HLS] 720p 1280x720 match failed. Falling back to first available stream.");
                const anyStream = masterText.match(/#EXT-X-STREAM-INF:.*BANDWIDTH=\d+[^\n]*\n(https:\/\/[^\s]+)/);
                videoUrl = anyStream ? anyStream[1] : null;
            }

            // Parse Audio
            // Find lines defining AUDIO media, extract URI. Attributes can be in any order.
            const audioLines = masterText.match(/^#EXT-X-MEDIA:.*TYPE=AUDIO.*/gm);
            if (audioLines && audioLines.length > 0) {
                // Use the first one found (minimum/default)
                const uriMatch = audioLines[0].match(/URI="(https:[^"]+)"/);
                audioUrl = uriMatch ? uriMatch[1] : null;
            }

            if (!audioUrl) {
                console.warn("[HLS] No separate AUDIO media found. Assuming muxed or fallback to video URL.");
                audioUrl = videoUrl;
            }

        } else {
            // Fallback path
            const result = await this.fetchStreamUrls(videoId, visitorData);
            videoUrl = result.videoUrl;
            audioUrl = result.audioUrl;
        }

        // For audio-only, we don't need videoUrl, just audioUrl
        if (!isAudioOnly && !videoUrl) throw new Error("Could not find suitable video stream in manifest");
        if (isAudioOnly && !audioUrl) throw new Error("Could not find audio stream for MP3 conversion");

        // 2. Start Download/Conversion
        const filename = await this.startDownload({
            videoUrl, audioUrl, videoId, duration, isAudioOnly, mp3Bitrate, videoTitle
        }, progressCallback);

        return filename;
    }

    async fetchStreamUrls(videoId, visitorData) {
        console.log(`[HLS] Fetching stream URLs for ${videoId}, VisitorData: ${visitorData ? visitorData.substring(0, 20) + '...' : 'N/A'}`);

        // iOS Client context for HLS
        const client = {
            clientName: "IOS",
            clientVersion: "19.29.1",
            deviceMake: "Apple",
            deviceModel: "iPhone16,2",
            userAgent: "com.google.ios.youtube/19.29.1 (iPhone16,2; U; CPU iOS 17_5_1 like Mac OS X)",
            osName: "iOS",
            osVersion: "17.5.1.21F90",
            visitorData: visitorData,
            hl: "en",
            gl: "US",
            utcOffsetMinutes: -420
        };

        // Use www.youtube.com instead of googleapis to leverage session cookies/origin better
        const apiUrl = "https://www.youtube.com/youtubei/v1/player?prettyPrint=false";

        const apiRes = await fetch(apiUrl, {
            method: "POST",
            headers: {
                "Content-Type": "application/json",
                "X-Goog-Visitor-Id": visitorData || "",
                "X-YouTube-Client-Name": "5",
                "X-YouTube-Client-Version": "19.29.1",
                "Origin": "https://www.youtube.com" // Try forcing origin
            },
            body: JSON.stringify({
                videoId: videoId,
                context: { client: client }
            })
        });

        if (!apiRes.ok) {
            const errText = await apiRes.text();
            console.error(`[HLS] API Error ${apiRes.status}: ${errText}`);
            throw new Error(`Failed to fetch InnerTube API: ${apiRes.status} ${apiRes.statusText}`);
        }

        const apiData = await apiRes.json();
        const hlsUrl = apiData.streamingData?.hlsManifestUrl;

        if (!hlsUrl) {
            console.warn("[HLS] API Response:", apiData);
            if (apiData.playabilityStatus) {
                console.warn("[HLS] Playability Status:", apiData.playabilityStatus);
                throw new Error(`Video unavailable: ${apiData.playabilityStatus.status}`);
            }
            throw new Error("No HLS Manifest URL found in API response");
        }

        console.log("[HLS] Manifest URL found:", hlsUrl);

        const masterText = await (await fetch(hlsUrl)).text();

        // Parse 720p Video
        const vidMatch = masterText.match(/#EXT-X-STREAM-INF:.*RESOLUTION=1280x720[^\n]*\n(https:\/\/[^\s]+)/);
        const videoUrl = vidMatch ? vidMatch[1] : null;

        // Parse Audio
        let audioUrl = null;
        const audioLines = masterText.match(/^#EXT-X-MEDIA:.*TYPE=AUDIO.*/gm);
        if (audioLines && audioLines.length > 0) {
            const uriMatch = audioLines[0].match(/URI="(https:[^"]+)"/);
            audioUrl = uriMatch ? uriMatch[1] : null;
        }

        if (!audioUrl) audioUrl = videoUrl;

        if (!videoUrl) throw new Error("Could not find 720p stream");

        return { videoUrl, audioUrl };
    }

    async startDownload(job, progressCallback) {
        await this.init();
        const libav = this.libav;
        const { videoUrl, audioUrl, videoId, isAudioOnly, mp3Bitrate, videoTitle } = job;

        // Determine output filename and format based on mode
        const outputFilename = isAudioOnly
            ? `output_${videoId}.mp3`
            : `output_${videoId}.mp4`;

        // 1. Setup Output to OPFS
        const opfsRoot = await navigator.storage.getDirectory();

        // Use a temp file for the writer
        const fileHandle = await opfsRoot.getFileHandle(outputFilename, { create: true });
        const accessHandle = await fileHandle.createSyncAccessHandle();

        // Implement the Writer interface required by LibAV's mkwriterdev
        const writer = {
            write: (position, data) => {
                accessHandle.write(data, { at: position });
            },
            close: () => {
                accessHandle.close();
            }
        };

        // Hook onwrite
        libav.onwrite = (name, position, data) => {
            if (name === outputFilename) {
                writer.write(position, data);
            }
            return data.length; // Return bytes written to satisfy WASM signature
        };

        // 2. Create the Virtual Device
        await libav.mkwriterdev(outputFilename);

        // 3. Construct FFmpeg Command based on mode
        const args = ["-analyzeduration", "10M"];

        if (isAudioOnly) {
            // Audio-only mode for MP3 conversion
            const inputUrl = audioUrl || videoUrl; // Use audio URL, fallback to video URL
            args.push("-f", "hls", "-i", `jsfetch:${inputUrl}`);
            args.push("-c:a", "libmp3lame");
            args.push("-b:a", `${mp3Bitrate}k`);
            args.push("-map", "0:a:0");
            console.log(`[Worker] MP3 mode: bitrate=${mp3Bitrate}kbps`);
        } else {
            // Video+Audio mode (existing logic)
            args.push("-f", "hls", "-i", `jsfetch:${videoUrl}`);

            if (audioUrl && audioUrl !== videoUrl) {
                args.push("-f", "hls", "-i", `jsfetch:${audioUrl}`);
                args.push("-c", "copy");
                args.push("-map", "0:v:0");
                args.push("-map", "1:a:0");
            } else {
                args.push("-c", "copy");
                args.push("-map", "0:v:0");
                args.push("-map", "0:a:0?");
            }
        }

        args.push("-y", outputFilename);

        console.log("Starting FFmpeg with:", args.join(" "));

        // 4. Run FFmpeg with Polling (Time-based progress)
        let polling = true;
        const totalDuration = parseInt(job.duration) || 0;

        const poller = async () => {
            if (!polling) return;
            try {
                const timeMs = await libav.ffmpeg_get_out_time_ms();
                // Send raw time 
                if (progressCallback && timeMs > 0) {
                    let pct = 0;
                    if (totalDuration > 0) {
                        pct = Math.min(100, Math.round((timeMs / 1000 / totalDuration) * 100));
                    }

                    progressCallback({
                        progress: pct,
                        message: `Processing... ${pct}%`
                    });
                }
            } catch (e) { }
            setTimeout(poller, 1000);
        };
        poller();

        const exitCode = await libav.ffmpeg(args);
        polling = false;

        // 5. Cleanup
        writer.close();
        await libav.unlink(outputFilename); // Unlink virtual file from WASM memory view

        if (exitCode !== 0) {
            throw new Error(`FFmpeg exited with code ${exitCode}`);
        }

        return outputFilename; // The file is now in OPFS with this name
    }

    // triggerDownload removed; handled in main thread
}

// Worker Entry Point
self.addEventListener('message', async (e) => {
    console.log("[Worker] Event Listener fired. Action:", e.data ? e.data.action : 'unknown');
    const { action, job } = e.data;
    if (action === 'start') {
        try {
            const { videoId, visitorData, hlsManifestUrl, videoUrl, audioUrl, duration, isAudioOnly, mp3Bitrate, videoTitle } = job;

            const onProgress = (data) => {
                self.postMessage({ type: 'progress', data });
            };

            const filename = await hlsDownloader.processFullDownload(
                videoId, visitorData, hlsManifestUrl, onProgress,
                videoUrl, audioUrl, duration,
                isAudioOnly, mp3Bitrate, videoTitle
            );

            self.postMessage({ type: 'complete', filename });
        } catch (err) {
            console.error("[Worker] Job execution error:", err);
            self.postMessage({ type: 'error', error: err.message });
        }
    }
});

const hlsDownloader = new HlsDownloader();
console.log("[Worker] Script fully loaded and listener registered.");
self.postMessage({ type: 'ready' });
