/**
 * 视频编辑后处理（对齐 Electron ipAgent:autoProcessVideo）
 * - processSilenceDetection：自动剪气口
 * - processVideoMixCut：画中画 / 混剪
 * - processGreenScreen：绿幕替换
 */
const fs = require("fs");
const path = require("path");
const os = require("os");
const { spawnSync } = require("child_process");
const {
  buildReplacementsFromMixCutSettings,
} = require("./mixcut_replacements.js");

const IMAGE_EXT = new Set([".jpg", ".jpeg", ".png", ".webp", ".bmp", ".gif"]);

function locateFfmpegBinary() {
  const fromEnv = process.env.AICLIENT_FFMPEG_PATH;
  if (fromEnv && fs.existsSync(fromEnv)) return fromEnv;
  return process.platform === "win32" ? "ffmpeg.exe" : "ffmpeg";
}

function locateFfprobeBinary() {
  const fromEnv = process.env.AICLIENT_FFPROBE_PATH;
  if (fromEnv && fs.existsSync(fromEnv)) return fromEnv;
  const ffmpeg = locateFfmpegBinary();
  const dir = path.dirname(ffmpeg);
  const sibling = path.join(dir, process.platform === "win32" ? "ffprobe.exe" : "ffprobe");
  if (fs.existsSync(sibling)) return sibling;
  return process.platform === "win32" ? "ffprobe.exe" : "ffprobe";
}

function execFfmpeg(args) {
  const ffmpeg = locateFfmpegBinary();
  const r = spawnSync(ffmpeg, args, { encoding: "utf8", windowsHide: true });
  if (r.status !== 0) {
    const err = (r.stderr || r.stdout || "").trim();
    throw new Error(err || `ffmpeg 退出码 ${r.status}`);
  }
}

function ensureOutputDir() {
  const dir = path.join(os.tmpdir(), "aiclient-video-edit");
  fs.mkdirSync(dir, { recursive: true });
  return dir;
}

function makeOutputPath(prefix, ext = "mp4") {
  return path.join(ensureOutputDir(), `${prefix}_${Date.now()}.${ext}`);
}

function parseDurationHms(text) {
  const marker = "Duration:";
  const idx = text.indexOf(marker);
  if (idx < 0) return 0;
  const rest = text.slice(idx + marker.length).trim();
  const timePart = rest.split(",")[0].trim();
  const parts = timePart.split(":").map(Number);
  if (parts.length < 3) return 0;
  const [h, m, s] = parts;
  if (![h, m, s].every((n) => Number.isFinite(n))) return 0;
  return h * 3600 + m * 60 + s;
}

function probeVideoDuration(videoPath) {
  const ffprobe = locateFfprobeBinary();
  const r = spawnSync(
    ffprobe,
    [
      "-v",
      "error",
      "-show_entries",
      "format=duration",
      "-of",
      "default=noprint_wrappers=1:nokey=1",
      videoPath,
    ],
    { encoding: "utf8", windowsHide: true },
  );
  if (r.status === 0) {
    const n = parseFloat(String(r.stdout || "").trim());
    if (Number.isFinite(n) && n > 0) return n;
  }
  const ffmpeg = locateFfmpegBinary();
  const r2 = spawnSync(ffmpeg, ["-i", videoPath], {
    encoding: "utf8",
    windowsHide: true,
  });
  return parseDurationHms(r2.stderr || "");
}

function probeVideoResolution(videoPath) {
  const ffprobe = locateFfprobeBinary();
  const r = spawnSync(
    ffprobe,
    [
      "-v",
      "error",
      "-select_streams",
      "v:0",
      "-show_entries",
      "stream=width,height",
      "-of",
      "csv=p=0:s=x",
      videoPath,
    ],
    { encoding: "utf8", windowsHide: true },
  );
  if (r.status === 0) {
    const line = String(r.stdout || "").trim();
    const m = line.match(/^(\d+)x(\d+)$/);
    if (m) {
      return { width: Number(m[1]), height: Number(m[2]) };
    }
  }
  return { width: 1920, height: 1080 };
}

function isImageFile(filePath) {
  return IMAGE_EXT.has(path.extname(filePath).toLowerCase());
}

function processSilenceDetection(inputVideo, opts = {}) {
  const silenceThreshold = Number(opts.silenceThreshold ?? -40);
  const silenceDuration = Number(opts.silenceDuration ?? 1);
  const minPauseDuration = Number(opts.minPauseDuration ?? 0.15);
  const minPauseDurationMs = Math.round(minPauseDuration * 1000);
  const outputVideo = makeOutputPath("silence");
  const audioFilter = `silenceremove=stop_periods=-1:stop_duration=${silenceDuration}:stop_threshold=${silenceThreshold}dB,adelay=${minPauseDurationMs}ms|${minPauseDurationMs}ms`;
  execFfmpeg([
    "-i",
    inputVideo,
    "-af",
    audioFilter,
    "-c:v",
    "copy",
    "-y",
    outputVideo,
  ]);
  return {
    outputVideo,
    message: `已删除超过 ${silenceDuration} 秒且低于 ${silenceThreshold}dB 的停顿`,
  };
}

function processGreenScreen(inputVideo, backgroundImage) {
  const outputVideo = makeOutputPath("greenscreen");
  const filterComplex = [
    "[1:v][0:v]scale2ref=flags=lanczos[bg][vid]",
    "[vid]format=yuva444p,chromakey=0x00FF00:0.28:0.05,chromakey=0x40FF40:0.12:0.15[fg]",
    "[bg][fg]overlay=0:0:shortest=1,unsharp=3:3:0.5:3:3:0.5,format=yuv420p[out]",
  ].join(";");
  execFfmpeg([
    "-i",
    inputVideo,
    "-loop",
    "1",
    "-i",
    backgroundImage,
    "-filter_complex",
    filterComplex,
    "-map",
    "[out]",
    "-map",
    "0:a?",
    "-c:v",
    "libx264",
    "-preset",
    "medium",
    "-crf",
    "18",
    "-c:a",
    "aac",
    "-b:a",
    "128k",
    "-shortest",
    "-y",
    outputVideo,
  ]);
  return { outputVideo, message: "绿幕替换完成" };
}

/** 对齐 Electron ipAgent:processVideoMixCut */
function processVideoMixCut({
  inputVideo,
  replacements,
  outputPath,
  videoResolution,
  videoDuration,
  displayMode = "pip",
}) {
  if (!replacements?.length) {
    throw new Error("混剪替换点为空");
  }
  for (const r of replacements) {
    if (!r.materialPath || !fs.existsSync(r.materialPath)) {
      throw new Error(`素材文件不存在: ${r.materialPath || "(空)"}`);
    }
  }

  let filterComplex = "";
  let inputFiles = [inputVideo];
  let currentStream = "[0:v]";

  const adjustedReplacements = replacements.map((r, i) => {
    let actualEndTime = r.startTime + Math.min(r.duration, r.materialDuration);
    for (let j = i + 1; j < replacements.length; j++) {
      const nextR = replacements[j];
      if (nextR.startTime < actualEndTime) {
        actualEndTime = nextR.startTime;
      }
    }
    return { ...r, actualEndTime };
  });

  for (let i = 0; i < adjustedReplacements.length; i++) {
    const r = adjustedReplacements[i];
    const inputIndex = i + 1;
    const materialStream = `[${inputIndex}:v]`;
    const trimmedMaterialStream = `[m_trim${i}]`;
    const processedMaterialStream = `[m${i}]`;
    const mode = r.displayMode || displayMode || "pip";
    let scaleFilter = "";
    let overlayParam = "";

    if (mode === "fullscreen") {
      scaleFilter = `scale=${videoResolution.width}:${videoResolution.height}:force_original_aspect_ratio=increase,crop=${videoResolution.width}:${videoResolution.height},boxblur=25:2`;
      overlayParam = "0:0";
    } else {
      const pipSizePercent = r.pipSizePercent || 30;
      const pipWidth = Math.round((videoResolution.width * pipSizePercent) / 100);
      const pipHeight = Math.round((videoResolution.height * pipSizePercent) / 100);
      const pipScaleMode = r.pipScaleMode || "fit";
      let pipScaleFilter = "";
      if (pipScaleMode === "fit") {
        pipScaleFilter = `scale=${pipWidth}:${pipHeight}:force_original_aspect_ratio=decrease`;
      } else if (pipScaleMode === "fill") {
        pipScaleFilter = `scale=${pipWidth}:${pipHeight}:force_original_aspect_ratio=increase,crop=${pipWidth}:${pipHeight}`;
      } else {
        pipScaleFilter = `scale=${pipWidth}:${pipHeight}`;
      }
      const pipPos = r.pipPosition || "bottom-right";
      let pipX = 0;
      let pipY = 0;
      switch (pipPos) {
        case "top-left":
          pipX = 10;
          pipY = 10;
          break;
        case "top-center":
          pipX = (videoResolution.width - pipWidth) / 2;
          pipY = 10;
          break;
        case "top-right":
          pipX = videoResolution.width - pipWidth - 10;
          pipY = 10;
          break;
        case "center-left":
          pipX = 10;
          pipY = (videoResolution.height - pipHeight) / 2;
          break;
        case "center":
          pipX = (videoResolution.width - pipWidth) / 2;
          pipY = (videoResolution.height - pipHeight) / 2;
          break;
        case "center-right":
          pipX = videoResolution.width - pipWidth - 10;
          pipY = (videoResolution.height - pipHeight) / 2;
          break;
        case "bottom-left":
          pipX = 10;
          pipY = videoResolution.height - pipHeight - 10;
          break;
        case "bottom-center":
          pipX = (videoResolution.width - pipWidth) / 2;
          pipY = videoResolution.height - pipHeight - 10;
          break;
        case "bottom-right":
        default:
          pipX = videoResolution.width - pipWidth - 10;
          pipY = videoResolution.height - pipHeight - 10;
          break;
      }
      scaleFilter = pipScaleFilter;
      overlayParam = `${Math.round(pipX)}:${Math.round(pipY)}`;
    }

    const actualDuration = Math.min(r.duration, r.materialDuration);
    const isImage = r.isImage ?? isImageFile(r.materialPath);

    if (isImage) {
      const fpsFilter = "fps=25";
      const setptsFilter = `setpts=PTS-STARTPTS+${r.startTime.toFixed(3)}/TB`;
      filterComplex += `${materialStream}${fpsFilter},${setptsFilter}${trimmedMaterialStream};`;
      if (mode === "fullscreen") {
        const originalMinimized = r.originalVideoMinimized;
        if (originalMinimized?.enabled) {
          const fullscreenScaleFilter = `scale=${videoResolution.width}:${videoResolution.height}:force_original_aspect_ratio=increase,crop=${videoResolution.width}:${videoResolution.height}`;
          filterComplex += `${trimmedMaterialStream}${fullscreenScaleFilter}${processedMaterialStream};`;
        } else {
          const simpleScaleFilter = `scale=${videoResolution.width}:${videoResolution.height}:force_original_aspect_ratio=decrease`;
          filterComplex += `${trimmedMaterialStream}${simpleScaleFilter}${processedMaterialStream};`;
        }
      } else {
        filterComplex += `${trimmedMaterialStream}${scaleFilter}${processedMaterialStream};`;
      }
    } else {
      const trimFilter = `trim=start=${(r.materialStartOffset || 0).toFixed(3)}:duration=${actualDuration.toFixed(3)}`;
      const setptsFilter = `setpts=PTS-STARTPTS+${r.startTime.toFixed(3)}/TB`;
      filterComplex += `${materialStream}${trimFilter},${setptsFilter}${trimmedMaterialStream};`;
      if (mode === "fullscreen") {
        const originalMinimized = r.originalVideoMinimized;
        if (originalMinimized?.enabled) {
          const fullscreenScaleFilter = `scale=${videoResolution.width}:${videoResolution.height}:force_original_aspect_ratio=increase,crop=${videoResolution.width}:${videoResolution.height}`;
          filterComplex += `${trimmedMaterialStream}${fullscreenScaleFilter}${processedMaterialStream};`;
        } else {
          const simpleScaleFilter = `scale=${videoResolution.width}:${videoResolution.height}:force_original_aspect_ratio=decrease`;
          filterComplex += `${trimmedMaterialStream}${simpleScaleFilter}${processedMaterialStream};`;
        }
      } else {
        filterComplex += `${trimmedMaterialStream}${scaleFilter}${processedMaterialStream};`;
      }
    }

    const outputStream = `[v${i}]`;
    const enableExpr = `gte(t,${r.startTime.toFixed(3)})*lt(t,${r.actualEndTime.toFixed(3)})`;

    if (mode === "fullscreen") {
      const originalMinimized = r.originalVideoMinimized;
      if (originalMinimized?.enabled) {
        const { shape, position, sizePercent } = originalMinimized;
        const originalSize = Math.floor(videoResolution.width * (sizePercent / 100));
        const splitStream1 = `[split${i}_1]`;
        const splitStream2 = `[split${i}_2]`;
        filterComplex += `${currentStream}split=2${splitStream1}${splitStream2};`;
        const origStream = `[orig${i}]`;
        let originalScaled = `${splitStream1}scale=${originalSize}:${originalSize}:force_original_aspect_ratio=increase,crop=${originalSize}:${originalSize}`;
        if (shape === "circle") {
          const radius = originalSize / 2;
          originalScaled += `,format=rgba,geq=r='r(X,Y)':g='g(X,Y)':b='b(X,Y)':a='if(lte(hypot(X-${radius},Y-${radius}),${radius}),255,0)'`;
        }
        originalScaled += origStream;
        filterComplex += `${originalScaled};`;
        const tmpStream = `[tmp${i}]`;
        filterComplex += `${splitStream2}${processedMaterialStream}overlay=0:0:enable='${enableExpr}'${tmpStream};`;
        const padding = 50;
        let x = 0;
        let y = 0;
        switch (position) {
          case "top-left":
            x = padding;
            y = padding;
            break;
          case "top-right":
            x = videoResolution.width - originalSize - padding;
            y = padding;
            break;
          case "bottom-left":
            x = padding;
            y = videoResolution.height - originalSize - padding;
            break;
          case "bottom-right":
            x = videoResolution.width - originalSize - padding;
            y = videoResolution.height - originalSize - padding;
            break;
          case "center":
            x = (videoResolution.width - originalSize) / 2;
            y = (videoResolution.height - originalSize) / 2;
            break;
          default:
            break;
        }
        filterComplex += `${tmpStream}${origStream}overlay=${Math.round(x)}:${Math.round(y)}:enable='${enableExpr}'${outputStream};`;
      } else {
        const blurredBgStream = `[blurred_bg${i}]`;
        filterComplex += `${currentStream}boxblur=25:2:enable='${enableExpr}'${blurredBgStream};`;
        filterComplex += `${blurredBgStream}${processedMaterialStream}overlay=(W-w)/2:(H-h)/2:enable='${enableExpr}'${outputStream};`;
      }
    } else {
      filterComplex += `${currentStream}${processedMaterialStream}overlay=${overlayParam}:enable='${enableExpr}'${outputStream};`;
    }
    inputFiles.push(r.materialPath);
    currentStream = outputStream;
  }

  filterComplex = filterComplex.slice(0, -1);
  filterComplex += `; ${currentStream}trim=start=0:duration=${videoDuration}[final_video]`;

  const ffmpegArgs = [
    ...inputFiles.flatMap((f) => ["-i", f]),
    "-filter_complex",
    filterComplex,
    "-map",
    "[final_video]",
    "-map",
    "0:a?",
    "-c:a",
    "copy",
    "-c:v",
    "libx264",
    "-preset",
    "medium",
    "-shortest",
    "-y",
    outputPath,
  ];
  execFfmpeg(ffmpegArgs);
  return { outputVideo: outputPath, message: "混剪处理完成" };
}

function normalizeReplacements(raw) {
  if (!Array.isArray(raw)) return [];
  return raw
    .map((r) => ({
      startTime: Number(r.startTime),
      duration: Number(r.duration),
      materialPath: String(r.materialPath || "").trim(),
      materialDuration: Number(r.materialDuration),
      materialStartOffset: Number(r.materialStartOffset || 0),
      displayMode: r.displayMode,
      pipPosition: r.pipPosition,
      pipSizePercent: r.pipSizePercent,
      pipScaleMode: r.pipScaleMode,
      isImage: r.isImage,
      originalVideoMinimized: r.originalVideoMinimized,
    }))
    .filter(
      (r) =>
        Number.isFinite(r.startTime) &&
        Number.isFinite(r.duration) &&
        r.duration > 0 &&
        r.materialPath &&
        Number.isFinite(r.materialDuration) &&
        r.materialDuration > 0,
    );
}

function pickReplacementsFromSettings(settings, videoDuration) {
  if (!settings || typeof settings !== "object") return [];
  const fromMixCut = buildReplacementsFromMixCutSettings(
    settings,
    videoDuration,
    probeVideoDuration,
  );
  if (fromMixCut.length) {
    return normalizeReplacements(fromMixCut);
  }
  if (Array.isArray(settings.replacements) && settings.replacements.length) {
    return normalizeReplacements(settings.replacements);
  }
  const simple = settings.simplePip;
  if (simple?.materialPath && fs.existsSync(simple.materialPath)) {
    const matDur =
      Number(simple.materialDuration) > 0
        ? Number(simple.materialDuration)
        : isImageFile(simple.materialPath)
          ? videoDuration
          : probeVideoDuration(simple.materialPath);
    const startTime = Number(simple.startTime || 0);
    const duration = Number(simple.duration || videoDuration);
    return normalizeReplacements([
      {
        startTime,
        duration,
        materialPath: simple.materialPath,
        materialDuration: matDur,
        materialStartOffset: Number(simple.materialStartOffset || 0),
        displayMode: simple.displayMode || settings.displayMode || "pip",
        pipPosition: simple.pipPosition || "bottom-right",
        pipSizePercent: simple.pipSizePercent ?? 30,
        pipScaleMode: simple.pipScaleMode || "fit",
        isImage: simple.isImage ?? isImageFile(simple.materialPath),
      },
    ]);
  }
  return [];
}

globalThis.__nodejsMain = async function main(params) {
  const p = params || {};
  const inputVideo = String(p.inputVideo || "").trim();
  if (!inputVideo || !fs.existsSync(inputVideo)) {
    return { success: false, error: "缺少或找不到输入视频" };
  }

  const steps = [];
  let current = inputVideo;

  try {
    if (p.autoCutBreath) {
      globalThis.__native?.emitProgress?.("正在剪气口…");
      const r = processSilenceDetection(current, {
        silenceThreshold: p.silenceThreshold,
        silenceDuration: p.silenceDuration,
        minPauseDuration: p.minPauseDuration,
      });
      current = r.outputVideo;
      steps.push({ step: "silence", ...r });
    }

    if (p.pipInPicture) {
      const settings = p.mixCutSettings || null;
      const replacements = pickReplacementsFromSettings(
        settings,
        probeVideoDuration(current),
      );
      if (!replacements.length) {
        return {
          success: false,
          error:
            "已启用画中画，但未配置混剪素材。请在本地配置 videoMixCutSettings（含 replacements 或 simplePip），或使用「选择画中画素材」",
        };
      }
      globalThis.__native?.emitProgress?.("正在混剪/画中画…");
      const videoDuration = probeVideoDuration(current);
      const videoResolution = probeVideoResolution(current);
      const outputPath = makeOutputPath("mixcut");
      const r = processVideoMixCut({
        inputVideo: current,
        replacements,
        outputPath,
        videoResolution,
        videoDuration,
        displayMode: settings?.displayMode || "pip",
      });
      current = r.outputVideo;
      steps.push({ step: "mixcut", ...r });
    }

    if (p.greenScreen) {
      const bg = String(p.backgroundImage || "").trim();
      if (!bg || !fs.existsSync(bg)) {
        return { success: false, error: "已启用绿幕切换，请先上传替换背景图" };
      }
      globalThis.__native?.emitProgress?.("正在绿幕替换…");
      const r = processGreenScreen(current, bg);
      current = r.outputVideo;
      steps.push({ step: "greenscreen", ...r });
    }

    return {
      success: true,
      videoPath: current,
      steps,
      message: steps.length ? steps[steps.length - 1].message : "未执行任何处理",
    };
  } catch (e) {
    return { success: false, error: e.message || String(e), partialVideo: current };
  }
};
