import { DataService } from "./DataService";

export type VideoMaterialRecord = {
    id?: number;
    name: string;
    path: string;
    type: "image" | "video";
    info: any;
    createdAt?: number;
    updatedAt?: number;
};

const IMAGE_EXTENSIONS = ["jpg", "jpeg", "png", "gif", "bmp", "webp"];
const VIDEO_EXTENSIONS = ["mp4", "mov", "avi", "mkv", "webm"];

type VideoCodecInfo = {
    codecName?: string;
    pixFmt?: string;
    width?: number;
    height?: number;
    duration?: number;
};

type PreparedImportFile = {
    path: string;
    info: Record<string, any>;
    saveExt?: string;
    cleanup?: () => Promise<void>;
};

export const VideoMaterialService = {
    tableName() {
        return "data_video_material";
    },
    decodeRecord(record: any): VideoMaterialRecord | null {
        if (!record) {
            return null;
        }
        return {
            ...record,
            info: record.info ? JSON.parse(record.info) : {},
        } as VideoMaterialRecord;
    },
    encodeRecord(record: Partial<VideoMaterialRecord>) {
        const nextRecord = { ...record } as any;
        if ("info" in nextRecord) {
            nextRecord.info = JSON.stringify(nextRecord.info || {});
        }
        return nextRecord;
    },
    getMaterialType(filePath: string): "image" | "video" {
        const ext = filePath.split(".").pop()?.toLowerCase() || "";
        return IMAGE_EXTENSIONS.includes(ext) ? "image" : "video";
    },
    getMaterialName(filePath: string) {
        const fileName = filePath.split(/[\\/]/).pop() || "未命名素材";
        return fileName.replace(/\.[^/.]+$/, "") || fileName;
    },
    getExtension(filePath: string): string {
        return filePath.split(".").pop()?.toLowerCase() || "";
    },
    isVideoFile(filePath: string): boolean {
        return VIDEO_EXTENSIONS.includes(this.getExtension(filePath));
    },
    async probeVideoCodec(filePath: string): Promise<VideoCodecInfo | null> {
        try {
            const output = await window.$mapi.app.spawnBinary("ffprobe", [
                "-v",
                "error",
                "-select_streams",
                "v:0",
                "-show_entries",
                "stream=codec_name,pix_fmt,width,height,duration",
                "-of",
                "json",
                filePath,
            ]);
            const parsed = JSON.parse(output || "{}");
            const stream = Array.isArray(parsed.streams) ? parsed.streams[0] : null;
            if (!stream) {
                return null;
            }
            return {
                codecName: stream.codec_name,
                pixFmt: stream.pix_fmt,
                width: Number(stream.width) || undefined,
                height: Number(stream.height) || undefined,
                duration: Number(stream.duration) || undefined,
            };
        } catch (error) {
            console.warn("[VideoMaterialService] probe video codec failed:", filePath, error);
            return null;
        }
    },
    needsCompatTranscode(filePath: string, info: VideoCodecInfo | null): boolean {
        if (!info) {
            return false;
        }

        return this.getExtension(filePath) !== "mp4"
            || (info.codecName || "").toLowerCase() !== "h264"
            || (info.pixFmt || "").toLowerCase() !== "yuv420p";
    },
    async transcodeToPreviewCompatibleMp4(filePath: string): Promise<string> {
        const outputPath = await window.$mapi.file.temp("mp4", "video-material-compatible");
        const args = [
            "-y",
            "-i",
            filePath,
            "-map",
            "0:v:0",
            "-map",
            "0:a?",
            "-c:v",
            "libx264",
            "-preset",
            "veryfast",
            "-crf",
            "20",
            "-pix_fmt",
            "yuv420p",
            "-c:a",
            "aac",
            "-b:a",
            "192k",
            "-movflags",
            "+faststart",
            outputPath,
        ];
        try {
            await window.$mapi.app.spawnBinary("ffmpeg", args);
        } catch (error) {
            await window.$mapi.file.deletes(outputPath, {
                isDataPath: false,
            });
            throw error;
        }
        return outputPath;
    },
    async prepareImportFile(filePath: string): Promise<PreparedImportFile> {
        const info: Record<string, any> = {
            sourcePath: filePath,
        };

        if (!this.isVideoFile(filePath)) {
            return { path: filePath, info };
        }

        const codecInfo = await this.probeVideoCodec(filePath);
        if (codecInfo) {
            info.originalVideoCodec = codecInfo.codecName;
            info.originalPixFmt = codecInfo.pixFmt;
            info.width = codecInfo.width;
            info.height = codecInfo.height;
            info.duration = codecInfo.duration;
        }

        if (!this.needsCompatTranscode(filePath, codecInfo)) {
            return { path: filePath, info };
        }

        const compatiblePath = await this.transcodeToPreviewCompatibleMp4(filePath);
        return {
            path: compatiblePath,
            info: {
                ...info,
                transcodedForPreview: true,
                previewVideoCodec: "h264",
                previewPixFmt: "yuv420p",
            },
            saveExt: "mp4",
            cleanup: async () => {
                await window.$mapi.file.deletes(compatiblePath, {
                    isDataPath: false,
                });
            },
        };
    },
    async list(): Promise<VideoMaterialRecord[]> {
        const records = await window.$mapi.db.select(
            `SELECT *
             FROM ${this.tableName()}
             ORDER BY updatedAt DESC, id DESC`
        );
        return records.map((record: any) => this.decodeRecord(record)).filter(Boolean) as VideoMaterialRecord[];
    },
    async insert(record: VideoMaterialRecord) {
        const nextRecord = this.encodeRecord(record);
        const fields = Object.keys(nextRecord).join(", ");
        const values = Object.values(nextRecord);
        const valuePlaceholders = values.map(() => "?").join(", ");
        return await window.$mapi.db.insert(
            `INSERT INTO ${this.tableName()} (${fields})
             VALUES (${valuePlaceholders})`,
            values
        );
    },
    async update(id: number, record: Partial<VideoMaterialRecord>) {
        const nextRecord = this.encodeRecord({
            ...record,
            updatedAt: Date.now(),
        });
        const fields = Object.keys(nextRecord)
            .map(key => `${key} = ?`)
            .join(", ");
        const values = Object.values(nextRecord);
        values.push(id);
        return await window.$mapi.db.update(
            `UPDATE ${this.tableName()}
             SET ${fields}
             WHERE id = ?`,
            values
        );
    },
    async delete(record: VideoMaterialRecord) {
        if (record.path) {
            await window.$mapi.file.hubDelete(record.path);
        }
        await window.$mapi.db.delete(
            `DELETE
             FROM ${this.tableName()}
             WHERE id = ?`,
            [record.id]
        );
    },
    async importFiles(filePaths: string[]) {
        const ids: number[] = [];
        for (const filePath of filePaths) {
            const prepared = await this.prepareImportFile(filePath);
            try {
                const savedPath = await DataService.saveFile(prepared.path, {
                    saveGroup: "video-material",
                    ext: prepared.saveExt,
                });
                const now = Date.now();
                const id = await this.insert({
                    name: this.getMaterialName(filePath),
                    path: savedPath,
                    type: this.getMaterialType(filePath),
                    info: prepared.info,
                    createdAt: now,
                    updatedAt: now,
                });
                ids.push(id);
            } finally {
                if (prepared.cleanup) {
                    await prepared.cleanup();
                }
            }
        }
        return ids;
    },
};
