// ============================================================
// canvas-ipc.ts — 无限画布 IPC 处理器（主进程）
//
// 职责：
//   - 目录扫描（fs.readdirSync + 递归遍历）
//   - sidecar JSON 读写
//   - 缩略图生成（sharp 可选依赖）
//   - 文件复制（导入操作）
// ============================================================

import { ipcMain } from 'electron';
import fs from 'node:fs';
import path from 'node:path';
import { BIDIRECTIONAL } from '../../../shared/constants/ipc-channels';
import { logger } from '../logger';
import sharp from "sharp";
// ---------- 类型 ----------

type StageType = 'Storyboard' | 'Keyframe' | 'Video' | 'Audio' | 'LipSync' | 'Assets';
type AssetTypeFilter = '全部' | '角色' | '道具' | '场景';

// ---------- 扫描算法 ----------

/** 递归遍历目录，收集所有匹配扩展名的文件 */
function walkDirectory(
    root: string,
    extensions: string[],
    recursive: boolean,
): Array<{ path: string; fileName: string; ext: string; fileSize: number; modifiedAt: string }> {
    const results: Array<{ path: string; fileName: string; ext: string; fileSize: number; modifiedAt: string }> = [];
    const extSet = new Set(extensions.map((e) => e.toLowerCase()));

    function walk(dir: string) {
        let entries: fs.Dirent[];
        try {
            entries = fs.readdirSync(dir, { withFileTypes: true });
        } catch {
            return; // 权限不足等，跳过该目录
        }

        for (const entry of entries) {
            // 跳过隐藏文件和文件夹
            if (entry.name.startsWith('.')) continue;

            const fullPath = path.join(dir, entry.name);
            if (entry.isDirectory()) {
                if (recursive) walk(fullPath);
            } else if (entry.isFile()) {
                const ext = path.extname(entry.name).slice(1).toLowerCase();
                if (extSet.has(ext)) {
                    try {
                        const stat = fs.statSync(fullPath);
                        results.push({
                            path: fullPath,
                            fileName: entry.name,
                            ext,
                            fileSize: stat.size,
                            modifiedAt: stat.mtime.toISOString(),
                        });
                    } catch {
                        // 文件不可访问，跳过
                    }
                }
            }
        }
    }

    walk(root);
    return results;
}

// ---------- Sidecar 操作 ----------

/** 生成 sidecar JSON 文件路径：同目录、同名、.json 扩展名 */
function sidecarPathForMedia(filePath: string): string {
    const dir = path.dirname(filePath);
    const baseName = path.basename(filePath, path.extname(filePath));
    return path.join(dir, `${baseName}.json`);
}

/** 读取旁加载元数据，文件不存在返回 null */
function readSidecarFile(filePath: string): object | null {
    const sidecarPath = sidecarPathForMedia(filePath);
    if (!fs.existsSync(sidecarPath)) return null;
    try {
        const content = fs.readFileSync(sidecarPath, 'utf-8');
        return JSON.parse(content);
    } catch {
        return null;
    }
}

/** 写入旁加载元数据（自动创建目录） */
function writeSidecarFile(filePath: string, metadata: object): void {
    const sidecarPath = sidecarPathForMedia(filePath);
    const dir = path.dirname(sidecarPath);
    if (!fs.existsSync(dir)) {
        fs.mkdirSync(dir, { recursive: true });
    }
    fs.writeFileSync(sidecarPath, JSON.stringify(metadata, null, 2), 'utf-8');
}

// ---------- MIME 类型推断 ----------

function getMimeType(filePath: string): string {
    const ext = path.extname(filePath).toLowerCase();
    const mimeMap: Record<string, string> = {
        '.jpg': 'image/jpeg',
        '.jpeg': 'image/jpeg',
        '.png': 'image/png',
        '.webp': 'image/webp',
        '.gif': 'image/gif',
        '.bmp': 'image/bmp',
        '.mp4': 'video/mp4',
        '.webm': 'video/webm',
        '.mkv': 'video/x-matroska',
        '.mov': 'video/quicktime',
        '.avi': 'video/x-msvideo',
        '.mp3': 'audio/mpeg',
        '.wav': 'audio/wav',
        '.ogg': 'audio/ogg',
        '.flac': 'audio/flac',
        '.aac': 'audio/aac',
        '.m4a': 'audio/mp4',
    };
    return mimeMap[ext] || 'application/octet-stream';
}

// ---------- 缩略图生成 ----------

/**
 * 尝试使用 sharp 库生成缩略图。
 *
 * 优先级：
 *   1. .cache/{filename}.webp  — 预生成 WEBP 缩略图（最快）
 *   2. .cache/{filename}       — 预生成 PNG 缩略图
 *   3. sharp                   — 实时缩放（可选依赖）
 *   4. 原始文件字节            — 浏览器端缩放回退
 */
async function generateThumbnailImpl(
    filePath: string,
    size: number,
): Promise<{ data: string; mimeType: string; width: number; height: number }> {
    const dir = path.dirname(filePath);
    const baseName = path.basename(filePath);
    const cacheDir = path.join(dir, '.cache');

    // ── 优先级 1：.cache/{filename}.webp ──
    const cachedWebp = path.join(cacheDir, `${baseName}.webp`);
    if (fs.existsSync(cachedWebp)) {
        const buffer = fs.readFileSync(cachedWebp);
        return {
            data: buffer.toString('base64'),
            mimeType: 'image/webp',
            width: 0,
            height: 0,
        };
    }

    // ── 优先级 2：.cache/{filename}（PNG 副本） ──
    const cachedPng = path.join(cacheDir, baseName);
    if (fs.existsSync(cachedPng)) {
        const buffer = fs.readFileSync(cachedPng);
        return {
            data: buffer.toString('base64'),
            mimeType: 'image/png',
            width: 0,
            height: 0,
        };
    }

    if (sharp) {
        const image = sharp(filePath);
        const metadata = await image.metadata();
        const resized = await image
            .resize(size, size, { fit: 'inside', withoutEnlargement: true })
            .jpeg({ quality: 85 })
            .toBuffer();
        return {
            data: resized.toString('base64'),
            mimeType: 'image/jpeg',
            width: metadata.width || 0,
            height: metadata.height || 0,
        };
    }

    // ── 优先级 4：回退原始文件 ──
    const buffer = fs.readFileSync(filePath);
    const mimeType = getMimeType(filePath);
    return {
        data: buffer.toString('base64'),
        mimeType,
        width: 0,
        height: 0,
    };
}

// ---------- 扫描根目录计算（移自主进程，因渲染进程沙箱无 fs 权限） ----------

/** 阶段 → 允许的扩展名 */
function getStageExtensions(stage: StageType): string[] {
    switch (stage) {
        case 'Storyboard':
        case 'Keyframe':
            return ['jpg', 'jpeg', 'png', 'webp', 'bmp', 'gif'];
        case 'Video':
            return ['mp4', 'webm', 'mov', 'avi', 'mkv'];
        case 'Audio':
        case 'LipSync':
            return ['mp3', 'wav', 'ogg', 'flac', 'aac', 'm4a'];
        case 'Assets':
            return ['jpg', 'jpeg', 'png', 'webp', 'bmp', 'gif', 'mp4', 'webm'];
    }
}

/** 镜头名匹配查询 */
function shotNameMatchesQuery(shotName: string, query: string): boolean {
    if (!query) return true;
    const upperShot = shotName.toUpperCase();
    const upperQuery = query.toUpperCase();
    if (upperShot.includes(upperQuery)) return true;
    if (/^\d+$/.test(query)) {
        const digits = upperShot.replace(/\D/g, '');
        if (digits.includes(query)) return true;
    }
    return false;
}

/** 计算扫描根目录列表 */
function buildScanRoots(params: {
    projectPath: string;
    stage: StageType;
    assetTypeFilter: AssetTypeFilter;
    shotQuery: string;
}): string[] {
    const { projectPath, stage, assetTypeFilter, shotQuery } = params;

    // ── 诊断：检查 projectPath 本身是否存在 ──
    const projectExists = fs.existsSync(projectPath);
    logger.info('canvas', 'buildScanRoots 诊断 — projectPath', {
        projectPath,
        exists: projectExists,
    });

    if (!projectExists) {
        logger.warn('canvas', '⚠️ 项目路径不可访问', undefined, { projectPath });
        // 尝试列出父目录内容以便排查
        const parent = path.dirname(projectPath);
        try {
            const siblings = fs.readdirSync(parent).filter((n) => !n.startsWith('.'));
            logger.info('canvas', `父目录 "${parent}" 内容（前 20 项）`, {
                count: siblings.length,
                items: siblings.slice(0, 20),
            });
        } catch {
            logger.warn('canvas', `无法读取父目录 "${parent}"`);
        }
        return [];
    }

    // ── 诊断：列出项目根目录内容 ──
    let projectEntries: string[] = [];
    try {
        projectEntries = fs.readdirSync(projectPath).filter((n) => !n.startsWith('.'));
    } catch {
        /* ignore */
    }
    logger.info('canvas', 'buildScanRoots 诊断 — 项目根目录内容（前 30 项）', {
        count: projectEntries.length,
        items: projectEntries.slice(0, 30),
    });

    if (stage === 'Assets') {
        const assetsDir = path.join(projectPath, 'assets');
        logger.info('canvas', 'buildScanRoots — Assets 模式', {
            assetsDir,
            exists: fs.existsSync(assetsDir),
            assetTypeFilter,
        });
        switch (assetTypeFilter) {
            case '角色': return [path.join(assetsDir, 'chr')];
            case '道具': return [path.join(assetsDir, 'prp')];
            case '场景': return [path.join(assetsDir, 'set')];
            default: return [assetsDir];
        }
    }

    const shotsDir = path.join(projectPath, 'shots');
    const shotsExists = fs.existsSync(shotsDir);
    logger.info('canvas', 'buildScanRoots 诊断 — shotsDir', {
        shotsDir,
        exists: shotsExists,
    });

    if (!shotsExists) {
        // 列出可能存在的子目录帮助用户对比
        const dirsInRoot = projectEntries.filter((name) => {
            try {
                return fs.statSync(path.join(projectPath, name)).isDirectory();
            } catch {
                return false;
            }
        });
        logger.warn('canvas', '⚠️ shots 目录不存在，项目根下的子目录：', undefined, {
            subdirs: dirsInRoot,
        });
        return [];
    }

    const roots: string[] = [];
    let episodes: string[];
    try {
        episodes = fs.readdirSync(shotsDir).filter((name) => {
            const full = path.join(shotsDir, name);
            return !name.startsWith('.') && fs.statSync(full).isDirectory();
        });
    } catch (err) {
        logger.warn('canvas', '无法读取 shots 目录', err instanceof Error ? err : undefined, {
            error: (err as Error).message,
        });
        return [];
    }

    logger.info('canvas', `buildScanRoots — 发现 ${episodes.length} 个剧集目录`, {
        episodes,
    });

    for (const ep of episodes) {
        const epDir = path.join(shotsDir, ep);
        let shotNames: string[];
        try {
            shotNames = fs.readdirSync(epDir).filter((name) => {
                const full = path.join(epDir, name);
                return !name.startsWith('.') && fs.statSync(full).isDirectory();
            });
        } catch (err) {
            logger.warn(
                'canvas',
                `无法读取剧集目录 "${ep}"`,
                err instanceof Error ? err : undefined,
                { error: (err as Error).message },
            );
            continue;
        }

        logger.info('canvas', `  📂 ${ep} → ${shotNames.length} 个镜头`, {
            shots: shotNames.slice(0, 20),
        });

        for (const shot of shotNames) {
            if (shotQuery && !shotNameMatchesQuery(shot, shotQuery)) continue;
            const stageDir = path.join(epDir, shot, stage);
            const stageExists = fs.existsSync(stageDir);
            if (stageExists) {
                roots.push(stageDir);
            }
            // 仅在前几个镜头时记录阶段目录存在性
            if (roots.length <= 3 && !stageExists) {
                logger.info('canvas', `    阶段目录不存在: ${stageDir}`);
            }
        }
    }

    logger.info('canvas', `buildScanRoots 最终结果：${roots.length} 个扫描根`, {
        roots: roots.slice(0, 10),
        stage,
    });

    return roots;
}

// ---------- 注册 ----------

export function registerCanvasIpcHandlers(): void {
    // ---- 扫描项目（主进程内部 buildScanRoots + walkDirectory） ----
    ipcMain.handle(
        BIDIRECTIONAL.CANVAS_SCAN_PROJECT,
        async (
            _event,
            params: {
                projectPath: string;
                stage: StageType;
                assetTypeFilter: AssetTypeFilter;
                shotQuery: string;
            },
        ) => {
            try {
                logger.info('canvas', '收到 CANVAS_SCAN_PROJECT', params);

                const roots = buildScanRoots(params);
                const extensions = getStageExtensions(params.stage);

                logger.info('canvas', `buildScanRoots 结果：${roots.length} 个扫描根`, {
                    roots: roots.length <= 10 ? roots : roots.slice(0, 10).concat(`... 共 ${roots.length} 个`),
                    extensions,
                    stage: params.stage,
                });

                if (roots.length === 0) {
                    logger.warn('canvas', '扫描根目录为空', undefined, {
                        projectPath: params.projectPath,
                        stage: params.stage,
                        shotQuery: params.shotQuery || '(无)',
                    });
                    return { success: true, data: [] };
                }

                const allResults: Array<{
                    path: string;
                    fileName: string;
                    ext: string;
                    fileSize: number;
                    modifiedAt: string;
                }> = [];

                for (const root of roots) {
                    const results = walkDirectory(root, extensions, true);
                    if (results.length > 0) {
                        logger.info('canvas', `  📁 ${root} → ${results.length} 个文件`);
                    }
                    allResults.push(...results);
                }

                logger.info(
                    'canvas',
                    `扫描项目完成：${allResults.length} 个文件（${roots.length} 个扫描根目录，阶段：${params.stage}）`,
                );
                return { success: true, data: allResults };
            } catch (err) {
                const msg = (err as Error).message;
                logger.warn('canvas', '项目扫描失败', err instanceof Error ? err : undefined);
                return { success: false, error: msg };
            }
        },
    );

    // ---- 目录扫描（保留低级 API：外部提供扫描根） ----
    // ---- 目录扫描 ----
    ipcMain.handle(
        BIDIRECTIONAL.CANVAS_SCAN_DIRECTORY,
        async (
            _event,
            params: { roots: string[]; extensions: string[]; recursive: boolean },
        ) => {
            try {
                const allResults: Array<{
                    path: string;
                    fileName: string;
                    ext: string;
                    fileSize: number;
                    modifiedAt: string;
                }> = [];
                for (const root of params.roots) {
                    if (!fs.existsSync(root)) {
                        logger.warn('canvas', `扫描根目录不存在，跳过：${root}`);
                        continue;
                    }
                    const results = walkDirectory(root, params.extensions, params.recursive);
                    allResults.push(...results);
                }
                logger.info(
                    'canvas',
                    `扫描完成：${allResults.length} 个文件（${params.roots.length} 个根目录）`,
                );
                return { success: true, data: allResults };
            } catch (err) {
                const msg = (err as Error).message;
                logger.warn('canvas', '目录扫描失败', err instanceof Error ? err : undefined);
                return { success: false, error: msg };
            }
        },
    );

    // ---- 读取 sidecar ----
    ipcMain.handle(
        BIDIRECTIONAL.CANVAS_READ_SIDECAR,
        async (_event, params: { filePath: string }) => {
            try {
                const data = readSidecarFile(params.filePath);
                return { success: true, data };
            } catch (err) {
                logger.warn('canvas', '读取 sidecar 失败', err instanceof Error ? err : undefined);
                return { success: false, error: (err as Error).message };
            }
        },
    );

    // ---- 写入 sidecar ----
    ipcMain.handle(
        BIDIRECTIONAL.CANVAS_WRITE_SIDECAR,
        async (_event, params: { filePath: string; metadata: object }) => {
            try {
                writeSidecarFile(params.filePath, params.metadata);
                return { success: true };
            } catch (err) {
                logger.warn('canvas', '写入 sidecar 失败', err instanceof Error ? err : undefined);
                return { success: false, error: (err as Error).message };
            }
        },
    );

    // ---- 生成缩略图 ----
    ipcMain.handle(
        BIDIRECTIONAL.CANVAS_GENERATE_THUMBNAIL,
        async (_event, params: { filePath: string; size: number }) => {
            try {
                const result = await generateThumbnailImpl(params.filePath, params.size);
                return { success: true, data: result };
            } catch (err) {
                logger.warn('canvas', '生成缩略图失败', err instanceof Error ? err : undefined);
                return { success: false, error: (err as Error).message };
            }
        },
    );

    // ---- 复制文件 ----
    ipcMain.handle(
        BIDIRECTIONAL.CANVAS_COPY_FILE,
        async (_event, params: { src: string; destDir: string; fileName: string }) => {
            try {
                if (!fs.existsSync(params.destDir)) {
                    fs.mkdirSync(params.destDir, { recursive: true });
                }
                const dest = path.join(params.destDir, params.fileName);
                fs.copyFileSync(params.src, dest);
                return { success: true, data: dest };
            } catch (err) {
                logger.warn('canvas', '复制文件失败', err instanceof Error ? err : undefined);
                return { success: false, error: (err as Error).message };
            }
        },
    );

    // ---- 获取图片尺寸（使用 sharp 或 image-size） ----
    ipcMain.handle(
        BIDIRECTIONAL.CANVAS_GET_IMAGE_SIZE,
        async (_event, params: { filePath: string }) => {
            try {
                if (sharp) {
                    const metadata = await sharp(params.filePath).metadata();
                    return {
                        success: true,
                        data: {
                            width: metadata.width || 0,
                            height: metadata.height || 0,
                        },
                    };
                }
                return { success: false, error: 'sharp 未安装，无法获取图片尺寸' };
            } catch (err) {
                logger.warn('canvas', '获取图片尺寸失败', err instanceof Error ? err : undefined);
                return { success: false, error: (err as Error).message };
            }
        },
    );

    // ---- 列出子文件夹（非递归，排除隐藏） ----
    ipcMain.handle(
        BIDIRECTIONAL.CANVAS_LIST_SUBDIRS,
        async (_event, params: { parentPath: string }) => {
            try {
                if (!fs.existsSync(params.parentPath)) {
                    logger.info('canvas', `listSubdirs: 路径不存在，返回空数组 — ${params.parentPath}`);
                    return { success: true, data: [] };
                }
                const entries = fs.readdirSync(params.parentPath, { withFileTypes: true });
                const dirs = entries
                    .filter((e) => e.isDirectory() && !e.name.startsWith('.'))
                    .map((e) => e.name);
                logger.info('canvas', `listSubdirs: ${dirs.length} 个子文件夹 — ${params.parentPath}`);
                return { success: true, data: dirs };
            } catch (err) {
                logger.warn('canvas', 'listSubdirs 失败', err instanceof Error ? err : undefined);
                return { success: false, error: (err as Error).message };
            }
        },
    );

    // ---- 扫描阶段文件 ----
    ipcMain.handle(
        BIDIRECTIONAL.CANVAS_SCAN_STAGE_FILES,
        async (
            _event,
            params: {
                projectPath: string;
                episode: string;
                shot: string;
                stage: string;
            },
        ) => {
            try {
                const stageDir = path.join(
                    params.projectPath,
                    'shots',
                    params.episode,
                    params.shot,
                    params.stage,
                );
                logger.info('canvas', `scanStageFiles: ${stageDir}`);
                if (!fs.existsSync(stageDir)) {
                    logger.info('canvas', `scanStageFiles: 阶段目录不存在 — ${stageDir}`);
                    return { success: true, data: [] };
                }
                const extensions = getStageExtensions(params.stage as StageType);
                const results = walkDirectory(stageDir, extensions, true);
                logger.info('canvas', `scanStageFiles: ${results.length} 个文件 — ${stageDir}`);
                return { success: true, data: results };
            } catch (err) {
                logger.warn('canvas', 'scanStageFiles 失败', err instanceof Error ? err : undefined);
                return { success: false, error: (err as Error).message };
            }
        },
    );

    // ---- 扫描资产文件（深度递归） ----
    ipcMain.handle(
        BIDIRECTIONAL.CANVAS_SCAN_ASSET_FILES,
        async (
            _event,
            params: { projectPath: string; assetTypeFilter: string },
        ) => {
            try {
                let assetsDir: string;
                switch (params.assetTypeFilter) {
                    case '角色':
                        assetsDir = path.join(params.projectPath, 'assets', 'chr');
                        break;
                    case '道具':
                        assetsDir = path.join(params.projectPath, 'assets', 'prp');
                        break;
                    case '场景':
                        assetsDir = path.join(params.projectPath, 'assets', 'set');
                        break;
                    default:
                        assetsDir = path.join(params.projectPath, 'assets');
                }
                logger.info('canvas', `scanAssetFiles: ${assetsDir} (filter=${params.assetTypeFilter})`);
                if (!fs.existsSync(assetsDir)) {
                    logger.info('canvas', `scanAssetFiles: 资产目录不存在 — ${assetsDir}`);
                    return { success: true, data: [] };
                }
                // 资产深度递归：图片 + 视频
                const extensions = ['jpg', 'jpeg', 'png', 'webp', 'bmp', 'gif', 'mp4', 'webm'];
                const results = walkDirectory(assetsDir, extensions, true);
                logger.info('canvas', `scanAssetFiles: ${results.length} 个文件 — ${assetsDir}`);
                return { success: true, data: results };
            } catch (err) {
                logger.warn('canvas', 'scanAssetFiles 失败', err instanceof Error ? err : undefined);
                return { success: false, error: (err as Error).message };
            }
        },
    );

    // ---- 创建目录 ----
    ipcMain.handle(
        BIDIRECTIONAL.CANVAS_CREATE_DIRECTORY,
        async (_event, params: { parentPath: string; dirName: string }) => {
            try {
                // 安全校验：拒绝包含危险字符的名称
                const dangerous = /[/\\:*?"<>|]/;
                if (!params.dirName || dangerous.test(params.dirName)) {
                    return {
                        success: false,
                        error: `无效的目录名: "${params.dirName}"`,
                    };
                }
                const fullPath = path.join(params.parentPath, params.dirName);
                if (fs.existsSync(fullPath)) {
                    return { success: false, error: `目录已存在: ${fullPath}` };
                }
                fs.mkdirSync(fullPath);
                logger.info('canvas', `createDirectory: ${fullPath}`);
                return { success: true, data: fullPath };
            } catch (err) {
                logger.warn('canvas', 'createDirectory 失败', err instanceof Error ? err : undefined);
                return { success: false, error: (err as Error).message };
            }
        },
    );

    // ---- 启动原生文件拖拽（拖放到 Photoshop/Explorer 等外部应用） ----
    ipcMain.handle(
        BIDIRECTIONAL.CANVAS_START_DRAG,
        async (_event, params: { filePath: string; fileName: string }) => {
            try {
                if (!fs.existsSync(params.filePath)) {
                    logger.warn('canvas', `startDrag: 文件不存在 — ${params.filePath}`);
                    return { success: false, error: '文件不存在' };
                }
                _event.sender.startDrag({
                    file: params.filePath,
                    icon: params.filePath,
                });
                logger.info('canvas', `startDrag: 已启动 — ${params.fileName}`);
                return { success: true };
            } catch (err) {
                const msg = (err as Error).message;
                logger.warn('canvas', 'startDrag 失败', err instanceof Error ? err : undefined);
                return { success: false, error: msg };
            }
        },
    );

    logger.info('canvas', '画布 IPC 处理器已注册（完整实现）');
}
