/**
 * Watermark / branding helpers  position math + FFmpeg-safe sanitization.
 * Never interpolate unsanitized user text into filter graphs.
 */

import {
  isWatermarkFontKey,
  type WatermarkFontKey,
} from "./fonts";

export const WATERMARK_POSITIONS = [
  "top-left",
  "top-right",
  "bottom-left",
  "bottom-right",
  "center",
] as const;

export type WatermarkPosition = (typeof WATERMARK_POSITIONS)[number];

export const WATERMARK_MODES = ["none", "default", "text", "logo"] as const;
export type WatermarkMode = (typeof WATERMARK_MODES)[number];

export type WatermarkSettings = {
  mode: WatermarkMode;
  text?: string | null;
  logoPath?: string | null;
  position: WatermarkPosition;
  /** Pixel offset from the chosen anchor (0–200). */
  offsetX: number;
  offsetY: number;
  /**
   * Typography (text mode).
   * `system` = FFmpeg default; curated keys map to assets/fonts; `custom` uses fontPath.
   */
  fontKey?: WatermarkFontKey;
  /** Absolute/staging path to uploaded .ttf/.otf when fontKey === "custom". */
  fontPath?: string | null;
};

export const DEFAULT_WATERMARK: WatermarkSettings = {
  mode: "default",
  text: null,
  logoPath: null,
  position: "bottom-right",
  offsetX: 20,
  offsetY: 20,
  fontKey: "system",
  fontPath: null,
};

export const WATERMARK_TEXT_MAX = 80;
export const WATERMARK_OFFSET_MIN = 0;
export const WATERMARK_OFFSET_MAX = 200;

export function isWatermarkPosition(v: unknown): v is WatermarkPosition {
  return typeof v === "string" && (WATERMARK_POSITIONS as readonly string[]).includes(v);
}

export function isWatermarkMode(v: unknown): v is WatermarkMode {
  return typeof v === "string" && (WATERMARK_MODES as readonly string[]).includes(v);
}

export function clampOffset(n: unknown, fallback = 20): number {
  const v = typeof n === "number" ? n : Number(n);
  if (!Number.isFinite(v)) return fallback;
  return Math.max(WATERMARK_OFFSET_MIN, Math.min(WATERMARK_OFFSET_MAX, Math.round(v)));
}

/**
 * Escape user text for FFmpeg drawtext.
 * Strips control chars; escapes \, :, ', %, and [.
 */
export function sanitizeDrawtext(raw: string): string {
  return raw
    .slice(0, WATERMARK_TEXT_MAX)
    .replace(/[\u0000-\u001f\u007f]/g, "")
    .replace(/\\/g, "\\\\")
    .replace(/:/g, "\\:")
    .replace(/'/g, "\\'")
    .replace(/%/g, "%%")
    .replace(/\[/g, "\\[");
}

/** FFmpeg overlay=x:y expressions for a scaled watermark layer `[wm]`. */
export function overlayXy(
  position: WatermarkPosition,
  offsetX: number,
  offsetY: number,
): { x: string; y: string } {
  const ox = clampOffset(offsetX);
  const oy = clampOffset(offsetY);
  switch (position) {
    case "top-left":
      return { x: String(ox), y: String(oy) };
    case "top-right":
      return { x: `W-w-${ox}`, y: String(oy) };
    case "bottom-left":
      return { x: String(ox), y: `H-h-${oy}` };
    case "center":
      return { x: `(W-w)/2+${ox}`, y: `(H-h)/2+${oy}` };
    case "bottom-right":
    default:
      return { x: `W-w-${ox}`, y: `H-h-${oy}` };
  }
}

/** drawtext x/y for text watermarks. */
export function drawtextXy(
  position: WatermarkPosition,
  offsetX: number,
  offsetY: number,
): { x: string; y: string } {
  const ox = clampOffset(offsetX);
  const oy = clampOffset(offsetY);
  switch (position) {
    case "top-left":
      return { x: String(ox), y: String(oy) };
    case "top-right":
      return { x: `w-text_w-${ox}`, y: String(oy) };
    case "bottom-left":
      return { x: String(ox), y: `h-th-${oy}` };
    case "center":
      return { x: `(w-text_w)/2+${ox}`, y: `(h-th)/2+${oy}` };
    case "bottom-right":
    default:
      return { x: `w-text_w-${ox}`, y: `h-th-${oy}` };
  }
}

/**
 * Build a drawtext filter segment (without leading comma).
 * `fontfileEscaped` must already be sanitized via sanitizeFontfileForFilter.
 */
export function buildDrawtextFilter(opts: {
  text: string;
  fontSize: number;
  fontColor?: string;
  position: WatermarkPosition;
  offsetX: number;
  offsetY: number;
  fontfileEscaped?: string | null;
}): string {
  const text = sanitizeDrawtext(opts.text);
  const { x, y } = drawtextXy(opts.position, opts.offsetX, opts.offsetY);
  const color = opts.fontColor ?? "white@0.9";
  const fontPart = opts.fontfileEscaped
    ? `:fontfile='${opts.fontfileEscaped}'`
    : "";
  return `drawtext=text='${text}'${fontPart}:fontsize=${opts.fontSize}:fontcolor=${color}:x=${x}:y=${y}`;
}

export function normalizeWatermarkSettings(
  input: Partial<WatermarkSettings> | null | undefined,
  includeWatermarkFallback: boolean,
): WatermarkSettings {
  if (!input) {
    return {
      ...DEFAULT_WATERMARK,
      mode: includeWatermarkFallback ? "default" : "none",
    };
  }
  const mode = isWatermarkMode(input.mode)
    ? input.mode
    : includeWatermarkFallback
      ? "default"
      : "none";
  const fontKey = isWatermarkFontKey(input.fontKey) ? input.fontKey : "system";
  return {
    mode,
    text: typeof input.text === "string" ? input.text.slice(0, WATERMARK_TEXT_MAX) : null,
    logoPath: typeof input.logoPath === "string" ? input.logoPath : null,
    position: isWatermarkPosition(input.position) ? input.position : "bottom-right",
    offsetX: clampOffset(input.offsetX, 20),
    offsetY: clampOffset(input.offsetY, 20),
    fontKey,
    fontPath: typeof input.fontPath === "string" ? input.fontPath : null,
  };
}

/** True when settings go beyond the default branding toggle. */
export function requiresCustomWatermarkEntitlement(settings: WatermarkSettings): boolean {
  if (settings.mode === "text" || settings.mode === "logo") return true;
  if (settings.mode === "none") return false;
  if (settings.position !== "bottom-right") return true;
  if (settings.offsetX !== 20 || settings.offsetY !== 20) return true;
  if (settings.fontKey && settings.fontKey !== "system") return true;
  if (settings.fontPath) return true;
  return false;
}

