// 本地音乐扫描模块
// 负责文件系统递归扫描和音乐文件元数据提取，通过 IPC 暴露给渲染进程

import * as crypto from 'crypto';
import { app, ipcMain } from 'electron';
import * as fs from 'fs';
import * as mm from 'music-metadata';
import * as os from 'os';
import * as path from 'path';

/** 支持的音频文件格式 */
const SUPPORTED_AUDIO_FORMATS = ['.mp3', '.flac', '.wav', '.ogg', '.m4a', '.aac'] as const;
const METADATA_PARSE_CONCURRENCY = Math.min(8, Math.max(2, os.cpus().length));
const MAX_COVER_BYTES = 8 * 1024 * 1024;

/** 封面缓存目录：userData/AudioCovers/<hash>.<ext> */
const COVER_DIR_NAME = 'AudioCovers';
let cachedCoverDir: string | null = null;

function getCoverDir(): string {
  if (cachedCoverDir) return cachedCoverDir;
  const dir = path.join(app.getPath('userData'), COVER_DIR_NAME);
  try {
    fs.mkdirSync(dir, { recursive: true });
  } catch (error) {
    console.error('创建封面目录失败:', error);
  }
  cachedCoverDir = dir;
  return dir;
}

/** 从 mime 类型推断文件扩展名 */
function extFromMime(mime: string | undefined): string {
  const sub = mime?.split('/')[1]?.split(';')[0]?.trim().toLowerCase();
  if (!sub) return 'bin';
  return sub === 'jpeg' ? 'jpg' : sub;
}

/**
 * 主进程返回的原始音乐元数据
 * 与渲染进程 LocalMusicMeta 类型保持一致
 */
type LocalMusicMeta = {
  /** 文件绝对路径 */
  filePath: string;
  /** 歌曲标题 */
  title: string;
  /** 艺术家名称 */
  artist: string;
  /** 专辑名称 */
  album: string;
  /** 时长（毫秒） */
  duration: number;
  /** 封面图片缓存文件绝对路径，无封面时为 null */
  coverPath: string | null;
  /** LRC 格式歌词文本，无歌词时为 null */
  lyrics: string | null;
  /** 文件大小（字节） */
  fileSize: number;
  /** 文件修改时间戳 */
  modifiedTime: number;
};

type ScannedMusicFile = {
  path: string;
  modifiedTime: number;
};

/**
 * 判断文件扩展名是否为支持的音频格式
 * @param ext 文件扩展名（含点号，如 .mp3）
 * @returns 是否为支持的格式
 */
function isSupportedFormat(ext: string): boolean {
  return (SUPPORTED_AUDIO_FORMATS as readonly string[]).includes(ext.toLowerCase());
}

/**
 * 从文件路径中提取歌曲标题（去除目录和扩展名）
 * @param filePath 文件路径
 * @returns 歌曲标题
 */
function extractTitleFromFilename(filePath: string): string {
  const basename = path.basename(filePath);
  const dotIndex = basename.lastIndexOf('.');
  if (dotIndex > 0) {
    return basename.slice(0, dotIndex);
  }
  return basename;
}

/**
 * 将封面图片落盘到 userData/AudioCovers/，返回绝对路径
 * 文件名按 sourceFilePath 的 sha256 + 推断扩展名拼成，幂等可覆盖
 * @param picture music-metadata 解析出的封面图片对象
 * @param sourceFilePath 音乐源文件绝对路径，用于生成稳定的封面文件名
 * @returns 封面文件绝对路径，无封面或写入失败返回 null
 */
async function extractCoverToFile(
  picture: mm.IPicture | undefined,
  sourceFilePath: string
): Promise<string | null> {
  if (!picture) {
    return null;
  }
  try {
    if (picture.data.length > MAX_COVER_BYTES) {
      console.warn(
        `封面超过大小上限被跳过: ${sourceFilePath} (${picture.data.length} bytes > ${MAX_COVER_BYTES})`
      );
      return null;
    }
    const ext = extFromMime(picture.format);
    const hash = crypto.createHash('sha256').update(sourceFilePath).digest('hex');
    const coverFile = path.join(getCoverDir(), `${hash}.${ext}`);

    // 直接覆盖写：本函数只在文件 mtime 变更时被调用（见 scanFolders 的 parseTargets），
    // 频率本就受守门；按 size 跳过会在"用户替换内嵌封面、新旧字节数恰好相等"时留旧图，
    // 单张封面几十~几百 KB，覆盖代价可忽略。
    await fs.promises.writeFile(coverFile, Buffer.from(picture.data));
    return coverFile;
  } catch (error) {
    console.error('封面落盘失败:', error);
    return null;
  }
}

/**
 * 从 music-metadata 解析结果中提取歌词文本
 * @param lyrics music-metadata 解析出的歌词数组
 * @returns 歌词文本，提取失败返回 null
 */
function extractLyrics(lyrics: mm.ILyricsTag[] | undefined): string | null {
  if (!lyrics || lyrics.length === 0) {
    return null;
  }
  try {
    // 优先取第一条歌词的文本内容
    const firstLyric = lyrics[0];
    return firstLyric?.text ?? null;
  } catch (error) {
    console.error('歌词提取失败:', error);
    return null;
  }
}

/**
 * 递归扫描指定文件夹，返回所有支持格式的音乐文件路径
 * @param folderPath 要扫描的文件夹路径
 * @returns 音乐文件绝对路径列表
 */
async function scanMusicFiles(folderPath: string): Promise<string[]> {
  const results: string[] = [];

  // 检查文件夹是否存在
  if (!fs.existsSync(folderPath)) {
    throw new Error(`文件夹不存在: ${folderPath}`);
  }

  // 检查是否为目录
  const stat = await fs.promises.stat(folderPath);
  if (!stat.isDirectory()) {
    throw new Error(`路径不是文件夹: ${folderPath}`);
  }

  /**
   * 递归遍历目录
   * @param dirPath 当前目录路径
   */
  async function walkDirectory(dirPath: string): Promise<void> {
    try {
      const entries = await fs.promises.readdir(dirPath, { withFileTypes: true });

      for (const entry of entries) {
        const fullPath = path.join(dirPath, entry.name);

        if (entry.isDirectory()) {
          // 递归扫描子目录
          await walkDirectory(fullPath);
        } else if (entry.isFile()) {
          // 检查文件扩展名是否为支持的音频格式
          const ext = path.extname(entry.name);
          if (isSupportedFormat(ext)) {
            results.push(fullPath);
          }
        }
      }
    } catch (error) {
      // 单个目录读取失败不中断整体扫描，记录错误后继续
      console.error(`扫描目录失败: ${dirPath}`, error);
    }
  }

  await walkDirectory(folderPath);
  return results;
}

/**
 * 递归扫描指定文件夹，返回包含修改时间的音乐文件信息
 * @param folderPath 要扫描的文件夹路径
 * @returns 音乐文件信息列表
 */
async function scanMusicFilesWithStats(folderPath: string): Promise<ScannedMusicFile[]> {
  const results: ScannedMusicFile[] = [];

  if (!fs.existsSync(folderPath)) {
    throw new Error(`文件夹不存在: ${folderPath}`);
  }

  const stat = await fs.promises.stat(folderPath);
  if (!stat.isDirectory()) {
    throw new Error(`路径不是文件夹: ${folderPath}`);
  }

  async function walkDirectory(dirPath: string): Promise<void> {
    try {
      const entries = await fs.promises.readdir(dirPath, { withFileTypes: true });

      for (const entry of entries) {
        const fullPath = path.join(dirPath, entry.name);

        if (entry.isDirectory()) {
          await walkDirectory(fullPath);
        } else if (entry.isFile()) {
          const ext = path.extname(entry.name);
          if (!isSupportedFormat(ext)) {
            continue;
          }

          try {
            const fileStat = await fs.promises.stat(fullPath);
            results.push({
              path: fullPath,
              modifiedTime: fileStat.mtimeMs
            });
          } catch (error) {
            console.error(`读取文件信息失败: ${fullPath}`, error);
          }
        }
      }
    } catch (error) {
      console.error(`扫描目录失败: ${dirPath}`, error);
    }
  }

  await walkDirectory(folderPath);
  return results;
}

/**
 * 解析单个音乐文件的元数据
 * 解析失败时使用 fallback 默认值（文件名作标题），不抛出异常
 * @param filePath 音乐文件绝对路径
 * @returns 音乐元数据对象
 */
async function parseMetadata(filePath: string): Promise<LocalMusicMeta> {
  // 获取文件信息（大小和修改时间）
  let fileSize = 0;
  let modifiedTime = 0;
  try {
    const stat = await fs.promises.stat(filePath);
    fileSize = stat.size;
    modifiedTime = stat.mtimeMs;
  } catch (error) {
    console.error(`获取文件信息失败: ${filePath}`, error);
  }

  // 构建 fallback 默认值
  const fallback: LocalMusicMeta = {
    filePath,
    title: extractTitleFromFilename(filePath),
    artist: '未知艺术家',
    album: '未知专辑',
    duration: 0,
    coverPath: null,
    lyrics: null,
    fileSize,
    modifiedTime
  };

  try {
    const metadata = await mm.parseFile(filePath);
    const { common, format } = metadata;

    return {
      filePath,
      title: common.title || fallback.title,
      artist: common.artist || fallback.artist,
      album: common.album || fallback.album,
      duration: format.duration ? Math.round(format.duration * 1000) : 0,
      coverPath: await extractCoverToFile(common.picture?.[0], filePath),
      lyrics: extractLyrics(common.lyrics),
      fileSize,
      modifiedTime
    };
  } catch (error) {
    // 解析失败使用 fallback，不中断流程
    console.error(`元数据解析失败，使用 fallback: ${filePath}`, error);
    return fallback;
  }
}

/**
 * 批量解析音乐文件元数据
 * 内部逐个调用 parseMetadata，单文件失败不影响其他文件
 * @param filePaths 音乐文件路径列表
 * @returns 元数据对象列表
 */
async function batchParseMetadata(filePaths: string[]): Promise<LocalMusicMeta[]> {
  if (filePaths.length === 0) {
    return [];
  }

  const results = new Array<LocalMusicMeta>(filePaths.length);
  const workerCount = Math.min(METADATA_PARSE_CONCURRENCY, filePaths.length);
  let index = 0;

  const workers = Array.from({ length: workerCount }, async () => {
    while (index < filePaths.length) {
      const current = index;
      index += 1;
      results[current] = await parseMetadata(filePaths[current]);
    }
  });

  await Promise.all(workers);
  return results;
}

/**
 * 初始化本地音乐扫描模块
 * 注册 IPC handler，供渲染进程调用
 */
export function initializeLocalMusicScanner(): void {
  // 扫描指定文件夹中的音乐文件
  ipcMain.handle('scan-local-music', async (_, folderPath: string) => {
    try {
      const files = await scanMusicFiles(folderPath);
      return { files, count: files.length };
    } catch (error: any) {
      console.error('扫描本地音乐失败:', error);
      return { error: error.message || '扫描失败' };
    }
  });

  // 扫描指定文件夹中的音乐文件（包含修改时间）
  ipcMain.handle('scan-local-music-with-stats', async (_, folderPath: string) => {
    try {
      const files = await scanMusicFilesWithStats(folderPath);
      return { files, count: files.length };
    } catch (error: any) {
      console.error('扫描本地音乐(含文件信息)失败:', error);
      return { error: error.message || '扫描失败' };
    }
  });

  // 批量解析音乐文件元数据
  ipcMain.handle('parse-local-music-metadata', async (_, filePaths: string[]) => {
    try {
      const metadataList = await batchParseMetadata(filePaths);
      return metadataList;
    } catch (error: any) {
      console.error('解析本地音乐元数据失败:', error);
      return [];
    }
  });
}
