"use strict";

/**
 * 关键词高亮 drawtext（对齐 Electron ZimuShengcheng inline 模式 + pulse-scale）
 */

const fs = require("fs");
const path = require("path");
const os = require("os");

const PUNCT_RE =
  /[，。！？；：、""''（）《》【】…—·,.!?;:'"()[\]{}<>]/g;

const DEFAULT_GROUP_META = {
  focus: {
    name: "重点词",
    effectId: "pulse-scale",
    styleConfig: {
      fontColor: "#FFD700",
      outlineColor: "#FF8C00",
      outlineWidth: 4,
    },
  },
  describe: {
    name: "描述词",
    effectId: "pulse-scale",
    styleConfig: {
      fontColor: "#5AC8FA",
      outlineColor: "#007AFF",
      outlineWidth: 3,
    },
  },
  action: {
    name: "行动词",
    effectId: "pulse-scale",
    styleConfig: {
      fontColor: "#34C759",
      outlineColor: "#248A3D",
      outlineWidth: 3,
    },
  },
  emotion: {
    name: "情感词",
    effectId: "pulse-scale",
    styleConfig: {
      fontColor: "#FF6B6B",
      outlineColor: "#C92A2A",
      outlineWidth: 3,
    },
  },
};

function parseKeywordLines(text) {
  return String(text || "")
    .split(/[\n,，、]+/)
    .map((s) => s.trim())
    .filter(Boolean);
}

/**
 * 从步骤 04 字段或显式 keywordGroups 构建分组
 * @param {Record<string, unknown>} params
 */
function buildKeywordGroups(params) {
  if (Array.isArray(params.keywordGroups) && params.keywordGroups.length > 0) {
    return params.keywordGroups
      .map((g) => normalizeKeywordGroup(g))
      .filter((g) => g.keywords.length > 0);
  }

  const sources = [
    ["focus", params.keywordsFocus],
    ["describe", params.keywordsDescribe],
    ["action", params.keywordsAction],
    ["emotion", params.keywordsEmotion],
  ];

  return sources
    .map(([id, raw]) => {
      const meta = DEFAULT_GROUP_META[id] || DEFAULT_GROUP_META.focus;
      const keywords = parseKeywordLines(raw);
      if (!keywords.length) return null;
      return {
        id,
        name: meta.name,
        effectId: meta.effectId,
        styleConfig: { ...meta.styleConfig },
        keywords,
      };
    })
    .filter(Boolean);
}

function normalizeKeywordGroup(group) {
  const g = group && typeof group === "object" ? group : {};
  const keywords = (Array.isArray(g.keywords) ? g.keywords : [])
    .map((k) => (typeof k === "string" ? k : String(k?.keyword || "")).trim())
    .filter(Boolean);
  return {
    id: String(g.id || g.groupId || "group"),
    name: String(g.name || g.groupName || "关键词"),
    effectId: String(g.effectId || g.effectConfig?.type || "pulse-scale"),
    styleConfig: g.styleConfig || g.styleOverride || {},
    keywords,
  };
}

function normalizeDisplayText(text) {
  return String(text || "")
    .replace(PUNCT_RE, "")
    .replace(/\s+/g, "");
}

function normalizeEffectType(effectId) {
  const id = String(effectId || "pulse-scale").toLowerCase();
  if (id.includes("pulse") || id.includes("scale")) return "pulse-scale";
  if (id.includes("slide")) return "slide";
  return "highlight";
}

function buildKeywordStyle(group, keywordItem) {
  const base = group.styleConfig || {};
  const override =
    keywordItem && typeof keywordItem === "object" ? keywordItem.styleOverride : null;
  const o = override || base;
  return {
    fontColor: o.fontColor || "#FFD700",
    outlineColor: o.outlineColor || "#FF8C00",
    outlineWidth: Number(o.outlineWidth) ?? 4,
    fontSize: Number(o.fontSize) || 0,
  };
}

/**
 * @param {Array<{ text: string, start: number, end: number }>} records 毫秒
 * @param {ReturnType<typeof buildKeywordGroups>} groups
 */
function matchKeywordsInRecords(records, groups) {
  const matches = [];
  const seen = new Set();

  for (const record of records) {
    const display = normalizeDisplayText(record.text);
    if (!display) continue;

    const segStartSec = record.start / 1000;
    const segEndSec = record.end / 1000;
    const dur = Math.max(0.05, segEndSec - segStartSec);

    for (const group of groups) {
      for (const kw of group.keywords) {
        const raw = typeof kw === "string" ? kw : String(kw?.keyword || "");
        const needle = normalizeDisplayText(raw);
        if (!needle) continue;

        let searchFrom = 0;
        while (searchFrom < display.length) {
          const idx = display.indexOf(needle, searchFrom);
          if (idx === -1) break;

          const dedupeKey = `${segStartSec.toFixed(3)}-${segEndSec.toFixed(3)}-${needle}-${idx}`;
          if (!seen.has(dedupeKey)) {
            seen.add(dedupeKey);
            const ratioStart = idx / display.length;
            const ratioEnd = (idx + needle.length) / display.length;
            const startSec = segStartSec + dur * ratioStart;
            const endSec = Math.max(startSec + 0.08, segStartSec + dur * ratioEnd);

            matches.push({
              keyword: needle,
              text: needle,
              lineText: display,
              lineLength: display.length,
              occurrenceStart: idx,
              occurrenceEnd: idx + needle.length,
              startSec,
              endSec,
              groupId: group.id,
              groupName: group.name,
              effectType: normalizeEffectType(
                (typeof kw === "object" && kw?.effectId) || group.effectId,
              ),
              style: buildKeywordStyle(group, kw),
            });
          }
          searchFrom = idx + Math.max(1, needle.length);
        }
      }
    }
  }

  return matches.sort((a, b) => a.startSec - b.startSec);
}

const WIN_FONT_MAP = {
  "microsoft yahei": ["msyh.ttc", "msyhbd.ttc"],
  "microsoft yahei bold": ["msyhbd.ttc", "msyh.ttc"],
  simhei: ["simhei.ttf"],
  "sim sun": ["simsun.ttc"],
  "source han sans": ["SourceHanSansSC-Regular.otf"],
};

function resolveFontFile(fontName) {
  const fromEnv = process.env.AICLIENT_SUBTITLE_FONT_PATH;
  if (fromEnv && fs.existsSync(fromEnv)) return fromEnv;

  const key = String(fontName || "Microsoft YaHei")
    .trim()
    .toLowerCase();
  const candidates = WIN_FONT_MAP[key] || WIN_FONT_MAP["microsoft yahei"];

  if (process.platform === "win32") {
    const fontsDir = path.join(process.env.WINDIR || "C:\\Windows", "Fonts");
    for (const file of candidates) {
      const full = path.join(fontsDir, file);
      if (fs.existsSync(full)) return full;
    }
  }

  const fallbacks = [
    "/usr/share/fonts/truetype/noto/NotoSansCJK-Regular.ttc",
    "/System/Library/Fonts/PingFang.ttc",
    "/System/Library/Fonts/Supplemental/Arial Unicode.ttf",
  ];
  for (const p of fallbacks) {
    if (fs.existsSync(p)) return p;
  }
  return null;
}

function escapeDrawtextPath(filePath) {
  return filePath
    .replace(/\\/g, "/")
    .replace(/:/g, "\\:")
    .replace(/'/g, "\\'");
}

function escapeDrawtextText(text) {
  return String(text || "")
    .replace(/\\/g, "\\\\")
    .replace(/'/g, "\\'")
    .replace(/:/g, "\\:")
    .replace(/,/g, "\\,");
}

function hexToDrawtextColor(hex) {
  const h = String(hex || "#FFFFFF").replace("#", "").slice(0, 6);
  if (h.length !== 6) return "white";
  return `0x${h.toUpperCase()}`;
}

function resolveSubtitleY(position, videoHeight) {
  const h = Math.max(360, Number(videoHeight) || 1080);
  if (position === "top") return "h*0.12";
  if (position === "center") return "(h-text_h)/2";
  return "h*0.82-text_h";
}

/**
 * @param {ReturnType<typeof matchKeywordsInRecords>[number]} match
 * @param {{ videoWidth: number, videoHeight: number, baseFontSize: number, fontfile: string, position: string }} layout
 */
function buildDrawtextForMatch(match, layout) {
  const { videoWidth, videoHeight, baseFontSize, fontfile, position } = layout;
  const style = match.style || {};
  const baseSize = style.fontSize > 0 ? style.fontSize : Math.round(baseFontSize * 1.45);
  const outlineW = Math.max(1, Math.round(style.outlineWidth || 3));
  const fontColor = hexToDrawtextColor(style.fontColor);
  const borderColor = hexToDrawtextColor(style.outlineColor);

  const lineLen = Math.max(1, match.lineLength);
  const charWidth = baseSize * 0.55;
  const lineWidth = Math.min(lineLen * charWidth, videoWidth * 0.88);
  const keywordMid = (match.occurrenceStart + match.occurrenceEnd) / 2;
  const offsetPx = (keywordMid / lineLen) * lineWidth - lineWidth / 2;
  const xExpr = `(w-text_w)/2+${offsetPx.toFixed(1)}`;
  const yExpr = resolveSubtitleY(position, videoHeight);

  const t0 = match.startSec.toFixed(3);
  const t1 = match.endSec.toFixed(3);
  /** 用 gte*lte 避免 between(t,a,b) 中逗号打断 -vf 滤镜链 */
  const enable = `gte(t\\,${t0})*lte(t\\,${t1})`;

  let fontsizePart = String(baseSize);
  if (match.effectType === "pulse-scale") {
    fontsizePart = `${baseSize}*(1+0.08*sin(8*PI*t))`;
  }

  const parts = [
    `fontfile='${escapeDrawtextPath(fontfile)}'`,
    `text='${escapeDrawtextText(match.text)}'`,
    `fontsize='${fontsizePart}'`,
    `fontcolor=${fontColor}`,
    `borderw=${outlineW}`,
    `bordercolor=${borderColor}`,
    `x='${xExpr}'`,
    `y='${yExpr}'`,
    `enable='${enable}'`,
  ];
  return `drawtext=${parts.join(":")}`;
}

/**
 * @param {ReturnType<typeof matchKeywordsInRecords>} matches
 * @param {{ videoWidth: number, videoHeight: number, subtitleStyle?: Record<string, unknown> }} opts
 * @returns {string | null} ffmpeg -vf 链（逗号分隔的 drawtext）
 */
function buildKeywordDrawtextFilter(matches, opts) {
  if (!matches.length) return null;

  const videoWidth = Math.max(320, Number(opts.videoWidth) || 1920);
  const videoHeight = Math.max(320, Number(opts.videoHeight) || 1080);
  const style = opts.subtitleStyle || {};
  const scale = Math.max(0.2, Math.min(2, (Number(style.fontSizeScale) || 100) / 100));
  const baseFontSize = Math.max(14, Math.round((Number(style.fontSize) || 35) * scale));
  const fontfile = resolveFontFile(style.fontName);
  if (!fontfile) return null;

  const layout = {
    videoWidth,
    videoHeight,
    baseFontSize,
    fontfile,
    position: String(style.position || "bottom"),
  };

  const filters = matches.map((m) => buildDrawtextForMatch(m, layout));
  const chain = filters.join(",");
  if (chain.length > 6000) {
    logKeywordWarn("drawtext 滤镜过长，仅保留前 40 个关键词");
    return filters.slice(0, 40).join(",");
  }
  return chain;
}

function logKeywordWarn(msg, fields) {
  if (globalThis.__native?.log) {
    globalThis.__native.log("warn", `[autoGenerateSubtitleAndBGM] ${msg}`, fields ?? null);
  }
}

/**
 * 从 ASS 底字中移除关键词字符，避免与 drawtext 高亮重复
 * @param {string} displayText 已去标点文本
 * @param {Array<{ occurrenceStart: number, occurrenceEnd: number }>} matches
 */
function stripKeywordsFromDisplay(displayText, matches) {
  const text = String(displayText || "");
  if (!text || !matches.length) return text;
  const chars = [...text];
  for (const m of matches) {
    const start = Math.max(0, m.occurrenceStart);
    const end = Math.min(chars.length, m.occurrenceEnd);
    for (let i = start; i < end; i++) chars[i] = "";
  }
  return chars.join("").trim();
}

/** @param {{ start: number, end: number, text?: string }} record 毫秒 */
function filterMatchesForRecord(record, matches) {
  const display = normalizeDisplayText(record.text);
  if (!display) return [];
  return matches.filter((m) => m.lineText === display);
}

/**
 * inline-emphasis：关键词用 ASS 内联样式，与正文同行同序（不用 drawtext 叠层）
 * @param {string} [mode]
 */
function isInlineKeywordRenderMode(mode) {
  return String(mode || "inline-emphasis") === "inline-emphasis";
}

module.exports = {
  buildKeywordGroups,
  matchKeywordsInRecords,
  buildKeywordDrawtextFilter,
  normalizeDisplayText,
  stripKeywordsFromDisplay,
  filterMatchesForRecord,
  isInlineKeywordRenderMode,
  resolveFontFile,
};
