/**
 * Art-track layout templates + blur background (Pro).
 * Cover/text anchors come from enum templates; only clamped fine-tuning offsets are allowed.
 */

export const LAYOUT_TEMPLATES = [
  "COVER_LEFT_TEXT_RIGHT",
  "COVER_TOP_TEXT_BOTTOM",
  "COVER_RIGHT_TEXT_LEFT",
  "CENTERED_COMPACT",
  "LOWER_LEFT_COVER_TEXT",
  "LOWER_RIGHT_COVER_TEXT",
] as const;

export type LayoutTemplate = (typeof LAYOUT_TEMPLATES)[number];

export const INVALID_LAYOUT_TEMPLATE_MESSAGE =
  "Invalid layout template. Refer to API documentation for valid enum values.";

export const BLUR_AMOUNT_MIN = 0;
export const BLUR_AMOUNT_MAX = 100;
export const BLUR_AMOUNT_DEFAULT = 55;

/** Visibility of the blurred cover fill over black (0 = solid black, 100 = full blur). */
export const BLUR_OPACITY_MIN = 0;
export const BLUR_OPACITY_MAX = 100;
export const BLUR_OPACITY_DEFAULT = 100;

export const TEXT_PADDING_MIN = 16;
export const TEXT_PADDING_MAX = 120;
export const TEXT_PADDING_DEFAULT = 48;

export const TITLE_ARTIST_GAP_MIN = 0;
export const TITLE_ARTIST_GAP_MAX = 64;
export const TITLE_ARTIST_GAP_DEFAULT = 10;

/** Fine-tune title/artist block within the template (not free-form canvas coords). */
export const TEXT_OFFSET_MIN = -120;
export const TEXT_OFFSET_MAX = 120;
export const TEXT_OFFSET_DEFAULT = 0;

/** Lower-corner cover size as a fraction of encode frame width. */
export const LOWER_CORNER_COVER_WIDTH_FRACTION = 0.23;

export function lowerCornerCoverSide(
  width: number,
  height: number,
  textPadding: number,
): number {
  const edge = clampTextPadding(textPadding);
  return Math.round(
    Math.min(width * LOWER_CORNER_COVER_WIDTH_FRACTION, height - edge * 2),
  );
}

export const ARTIST_MAX = 80;
export const SONG_TITLE_MAX = 120;

export type LayoutSettings = {
  /** When null, classic letterbox (no art-track layout). */
  template: LayoutTemplate | null;
  /**
   * Classic letterbox only: fill letterbox bars with a blurred cover.
   * Ignored when `template` is set (art-track always uses a blur fill).
   */
  blurFill: boolean;
  /** 0-100 - FFmpeg gblur intensity. */
  blurAmount: number;
  /** 0–100 → how visible the blurred fill is (vs black). */
  blurOpacity: number;
  /** Pixel padding / gap around cover + text. */
  textPadding: number;
  /** Extra pixels between title and artist lines. */
  titleArtistGap: number;
  /** Shift text block horizontally within the template. */
  textOffsetX: number;
  /** Shift text block vertically within the template. */
  textOffsetY: number;
  /** Bold weight for the on-video song title (artist stays regular). */
  titleBold: boolean;
};

export const DEFAULT_LAYOUT: LayoutSettings = {
  template: null,
  blurFill: false,
  blurAmount: BLUR_AMOUNT_DEFAULT,
  blurOpacity: BLUR_OPACITY_DEFAULT,
  textPadding: TEXT_PADDING_DEFAULT,
  titleArtistGap: TITLE_ARTIST_GAP_DEFAULT,
  textOffsetX: TEXT_OFFSET_DEFAULT,
  textOffsetY: TEXT_OFFSET_DEFAULT,
  titleBold: true,
};

export function isLayoutTemplate(v: unknown): v is LayoutTemplate {
  return typeof v === "string" && (LAYOUT_TEMPLATES as readonly string[]).includes(v);
}

export function clampBlurAmount(n: unknown, fallback = BLUR_AMOUNT_DEFAULT): number {
  const v = typeof n === "number" ? n : Number(n);
  if (!Number.isFinite(v)) return fallback;
  return Math.max(BLUR_AMOUNT_MIN, Math.min(BLUR_AMOUNT_MAX, Math.round(v)));
}

export function clampBlurOpacity(n: unknown, fallback = BLUR_OPACITY_DEFAULT): number {
  const v = typeof n === "number" ? n : Number(n);
  if (!Number.isFinite(v)) return fallback;
  return Math.max(BLUR_OPACITY_MIN, Math.min(BLUR_OPACITY_MAX, Math.round(v)));
}

export function clampTextPadding(n: unknown, fallback = TEXT_PADDING_DEFAULT): number {
  const v = typeof n === "number" ? n : Number(n);
  if (!Number.isFinite(v)) return fallback;
  return Math.max(TEXT_PADDING_MIN, Math.min(TEXT_PADDING_MAX, Math.round(v)));
}

export function clampTitleArtistGap(n: unknown, fallback = TITLE_ARTIST_GAP_DEFAULT): number {
  const v = typeof n === "number" ? n : Number(n);
  if (!Number.isFinite(v)) return fallback;
  return Math.max(TITLE_ARTIST_GAP_MIN, Math.min(TITLE_ARTIST_GAP_MAX, Math.round(v)));
}

export function clampTextOffset(n: unknown, fallback = TEXT_OFFSET_DEFAULT): number {
  const v = typeof n === "number" ? n : Number(n);
  if (!Number.isFinite(v)) return fallback;
  return Math.max(TEXT_OFFSET_MIN, Math.min(TEXT_OFFSET_MAX, Math.round(v)));
}

export function blurToBoxblur(blurAmount: number): { radius: number; power: number } | null {
  const amount = clampBlurAmount(blurAmount, 0);
  if (amount <= 0) return null;
  const radius = Math.max(1, Math.round((amount / 100) * 50));
  const power = Math.max(1, Math.min(4, Math.ceil(amount / 25)));
  return { radius, power };
}

/**
 * Still-image cover inputs (-loop 1 -r 1) respond poorly to boxblur; gblur is
 * reliable and maps 0-100 to a visible sigma range.
 */
export function boxblurFilterSegment(blurAmount: number): string {
  const amount = clampBlurAmount(blurAmount, 0);
  if (amount <= 0) return "";
  const sigma = Math.max(0.8, (amount / 100) * 24);
  return `gblur=sigma=${sigma.toFixed(2)}`;
}

/** Solid black used when fading blur opacity; must outlast any track length. */
const BLACK_FRAME = (width: number, height: number, label: string) =>
  `color=c=black:s=${width}x${height}:d=86400[${label}]`;

type RawLayoutInput = {
  template?: unknown;
  layoutTemplate?: unknown;
  layout_template?: unknown;
  blurFill?: unknown;
  blur_fill?: unknown;
  blurAmount?: unknown;
  blur_amount?: unknown;
  blurOpacity?: unknown;
  blur_opacity?: unknown;
  textPadding?: unknown;
  text_padding?: unknown;
  titleArtistGap?: unknown;
  title_artist_gap?: unknown;
  textOffsetX?: unknown;
  text_offset_x?: unknown;
  textOffsetY?: unknown;
  text_offset_y?: unknown;
  titleBold?: unknown;
  title_bold?: unknown;
  /** Rejected  clients must not send free-form cover coordinates. */
  x?: unknown;
  y?: unknown;
  coverX?: unknown;
  coverY?: unknown;
  offsetX?: unknown;
  offsetY?: unknown;
};

export function normalizeLayoutSettings(
  input: RawLayoutInput | Partial<LayoutSettings> | null | undefined,
): LayoutSettings {
  if (!input) return { ...DEFAULT_LAYOUT };

  const forbidden = ["x", "y", "coverX", "coverY", "offsetX", "offsetY"] as const;
  for (const k of forbidden) {
    if ((input as RawLayoutInput)[k] !== undefined) {
      throw new Error(INVALID_LAYOUT_TEMPLATE_MESSAGE);
    }
  }

  const raw =
    (input as RawLayoutInput).template ??
    (input as RawLayoutInput).layoutTemplate ??
    (input as RawLayoutInput).layout_template;

  let template: LayoutTemplate | null = null;
  if (raw !== undefined && raw !== null && raw !== "" && raw !== "CLASSIC" && raw !== "classic") {
    if (!isLayoutTemplate(raw)) {
      throw new Error(INVALID_LAYOUT_TEMPLATE_MESSAGE);
    }
    template = raw;
  }

  const blurRaw =
    (input as RawLayoutInput).blurAmount ??
    (input as RawLayoutInput).blur_amount ??
    (input as LayoutSettings).blurAmount;
  const opacityRaw =
    (input as RawLayoutInput).blurOpacity ??
    (input as RawLayoutInput).blur_opacity ??
    (input as LayoutSettings).blurOpacity;
  const padRaw =
    (input as RawLayoutInput).textPadding ??
    (input as RawLayoutInput).text_padding ??
    (input as LayoutSettings).textPadding;
  const gapRaw =
    (input as RawLayoutInput).titleArtistGap ??
    (input as RawLayoutInput).title_artist_gap ??
    (input as LayoutSettings).titleArtistGap;
  const oxRaw =
    (input as RawLayoutInput).textOffsetX ??
    (input as RawLayoutInput).text_offset_x ??
    (input as LayoutSettings).textOffsetX;
  const oyRaw =
    (input as RawLayoutInput).textOffsetY ??
    (input as RawLayoutInput).text_offset_y ??
    (input as LayoutSettings).textOffsetY;
  const boldRaw =
    (input as RawLayoutInput).titleBold ??
    (input as RawLayoutInput).title_bold ??
    (input as LayoutSettings).titleBold;
  const blurFillRaw =
    (input as RawLayoutInput).blurFill ??
    (input as RawLayoutInput).blur_fill ??
    (input as LayoutSettings).blurFill;

  return {
    template,
    blurFill: template !== null ? false : Boolean(blurFillRaw),
    blurAmount: clampBlurAmount(blurRaw, BLUR_AMOUNT_DEFAULT),
    blurOpacity: clampBlurOpacity(opacityRaw, BLUR_OPACITY_DEFAULT),
    textPadding: clampTextPadding(padRaw, TEXT_PADDING_DEFAULT),
    titleArtistGap: clampTitleArtistGap(gapRaw, TITLE_ARTIST_GAP_DEFAULT),
    textOffsetX: clampTextOffset(oxRaw, TEXT_OFFSET_DEFAULT),
    textOffsetY: clampTextOffset(oyRaw, TEXT_OFFSET_DEFAULT),
    titleBold: boldRaw === undefined || boldRaw === null ? true : Boolean(boldRaw),
  };
}

export function requiresArtTrackLayoutEntitlement(settings: LayoutSettings): boolean {
  return settings.template !== null;
}

export type LayoutGeometry = {
  coverMaxW: number;
  coverMaxH: number;
  coverX: string;
  coverY: string;
  titleFontSize: number;
  artistFontSize: number;
  titleX: string;
  titleY: string;
  artistX: string;
  artistY: string;
};

function applyTextOffsets(
  geo: LayoutGeometry,
  textOffsetX: number,
  textOffsetY: number,
): LayoutGeometry {
  const ox = clampTextOffset(textOffsetX);
  const oy = clampTextOffset(textOffsetY);
  if (ox === 0 && oy === 0) return geo;

  const shiftX = (expr: string) => {
    if (expr === "(w-text_w)/2") return `(w-text_w)/2+${ox}`;
    if (/^-?\d+$/.test(expr)) return String(Number(expr) + ox);
    return `${expr}+${ox}`;
  };
  const shiftY = (expr: string) => {
    if (/^-?\d+$/.test(expr)) return String(Number(expr) + oy);
    return `${expr}+${oy}`;
  };

  return {
    ...geo,
    titleX: shiftX(geo.titleX),
    titleY: shiftY(geo.titleY),
    artistX: shiftX(geo.artistX),
    artistY: shiftY(geo.artistY),
  };
}

/** Pixel geometry  template + padding + title/artist gap + text offsets. */
export function computeLayoutGeometry(
  template: LayoutTemplate,
  width: number,
  height: number,
  textPadding: number,
  titleArtistGap: number = TITLE_ARTIST_GAP_DEFAULT,
  textOffsetX: number = 0,
  textOffsetY: number = 0,
): LayoutGeometry {
  const pad = clampTextPadding(textPadding);
  const gap = clampTitleArtistGap(titleArtistGap);
  const titleFontSize = Math.max(22, Math.round(width * 0.032));
  const artistFontSize = Math.max(16, Math.round(width * 0.02));
  const lineGap = gap;

  let base: LayoutGeometry;

  switch (template) {
    case "COVER_LEFT_TEXT_RIGHT": {
      const coverMaxW = Math.round(width * 0.42);
      const coverMaxH = height - pad * 2;
      const textX = pad + coverMaxW + pad;
      const midY = Math.round(height / 2);
      base = {
        coverMaxW,
        coverMaxH,
        coverX: String(pad),
        coverY: "(H-h)/2",
        titleFontSize,
        artistFontSize,
        titleX: String(textX),
        titleY: String(midY - titleFontSize - Math.round(lineGap / 2)),
        artistX: String(textX),
        artistY: String(midY + Math.round(lineGap / 2)),
      };
      break;
    }
    case "COVER_RIGHT_TEXT_LEFT": {
      const coverMaxW = Math.round(width * 0.42);
      const coverMaxH = height - pad * 2;
      const textX = pad;
      const midY = Math.round(height / 2);
      base = {
        coverMaxW,
        coverMaxH,
        coverX: `W-w-${pad}`,
        coverY: "(H-h)/2",
        titleFontSize,
        artistFontSize,
        titleX: String(textX),
        titleY: String(midY - titleFontSize - Math.round(lineGap / 2)),
        artistX: String(textX),
        artistY: String(midY + Math.round(lineGap / 2)),
      };
      break;
    }
    case "COVER_TOP_TEXT_BOTTOM": {
      // Near full width  avoid empty side gutters next to the cover
      const coverMaxW = width - pad * 2;
      const coverMaxH = Math.round(height * 0.56);
      const textBlockTop = pad + coverMaxH + Math.round(pad * 0.55);
      base = {
        coverMaxW,
        coverMaxH,
        coverX: "(W-w)/2",
        coverY: String(pad),
        titleFontSize,
        artistFontSize,
        titleX: "(w-text_w)/2",
        titleY: String(textBlockTop),
        artistX: "(w-text_w)/2",
        artistY: String(textBlockTop + titleFontSize + lineGap),
      };
      break;
    }
    case "CENTERED_COMPACT": {
      const side = Math.round(Math.min(width, height) * 0.4);
      const stackH = side + pad + titleFontSize + lineGap + artistFontSize;
      const stackTop = Math.round((height - stackH) / 2);
      const coverY = Math.max(pad, stackTop);
      const titleY = coverY + side + Math.round(pad * 0.55);
      base = {
        coverMaxW: side,
        coverMaxH: side,
        coverX: "(W-w)/2",
        coverY: String(coverY),
        titleFontSize,
        artistFontSize,
        titleX: "(w-text_w)/2",
        titleY: String(titleY),
        artistX: "(w-text_w)/2",
        artistY: String(titleY + titleFontSize + lineGap),
      };
      break;
    }
    case "LOWER_LEFT_COVER_TEXT": {
      // Equal left + bottom inset so the cover corner sits on a true diagonal from (0, H).
      const edge = pad;
      const side = lowerCornerCoverSide(width, height, pad);
      const coverTextGap = Math.max(10, Math.round(pad * 0.55));
      const textX = edge + side + coverTextGap;
      const coverTop = height - edge - side;
      const textBlockH = titleFontSize + lineGap + artistFontSize;
      const textMidY = coverTop + Math.round(side / 2);
      const titleY = Math.max(edge, textMidY - Math.round(textBlockH / 2));
      base = {
        coverMaxW: side,
        coverMaxH: side,
        coverX: String(edge),
        coverY: `H-h-${edge}`,
        titleFontSize,
        artistFontSize,
        titleX: String(textX),
        titleY: String(titleY),
        artistX: String(textX),
        artistY: String(titleY + titleFontSize + lineGap),
      };
      break;
    }
    case "LOWER_RIGHT_COVER_TEXT": {
      // Equal right + bottom inset (mirror of lower-left diagonal).
      const edge = pad;
      const side = lowerCornerCoverSide(width, height, pad);
      const coverTextGap = Math.max(10, Math.round(pad * 0.55));
      const coverTop = height - edge - side;
      const textBlockH = titleFontSize + lineGap + artistFontSize;
      const textMidY = coverTop + Math.round(side / 2);
      const titleY = Math.max(edge, textMidY - Math.round(textBlockH / 2));
      const textInset = edge + side + coverTextGap;
      base = {
        coverMaxW: side,
        coverMaxH: side,
        coverX: `W-w-${edge}`,
        coverY: `H-h-${edge}`,
        titleFontSize,
        artistFontSize,
        titleX: `W-text_w-${textInset}`,
        titleY: String(titleY),
        artistX: `W-text_w-${textInset}`,
        artistY: String(titleY + titleFontSize + lineGap),
      };
      break;
    }
    default: {
      throw new Error(INVALID_LAYOUT_TEMPLATE_MESSAGE);
    }
  }

  return applyTextOffsets(base, textOffsetX, textOffsetY);
}

export function isLowerCornerTemplate(
  template: LayoutTemplate | null | undefined,
): boolean {
  return template === "LOWER_LEFT_COVER_TEXT" || template === "LOWER_RIGHT_COVER_TEXT";
}

export function buildArtTrackFilterComplex(opts: {
  width: number;
  height: number;
  layout: LayoutSettings;
  titleEscaped: string;
  artistEscaped: string | null;
  /** Regular-weight font for artist (and title when not bold / no bold file). */
  fontfileEscaped?: string | null;
  /** Bold font for title when `layout.titleBold` and a bold face is available. */
  titleFontfileEscaped?: string | null;
  /**
   * When set, use this FFmpeg input index as the blur-fill source instead of
   * splitting the cover (`[0:v]`). Cover stays on input 0 as the sharp overlay.
   */
  separateBackgroundInputIndex?: number | null;
}): string {
  const { width: W, height: H, layout } = opts;
  if (!layout.template) {
    throw new Error("Art-track filter requires a layout template");
  }

  const geo = computeLayoutGeometry(
    layout.template,
    W,
    H,
    layout.textPadding,
    layout.titleArtistGap,
    layout.textOffsetX,
    layout.textOffsetY,
  );
  const blurSeg = boxblurFilterSegment(layout.blurAmount);
  const bgChain = blurSeg
    ? `scale=${W}:${H}:force_original_aspect_ratio=increase,crop=${W}:${H},${blurSeg}`
    : `scale=${W}:${H}:force_original_aspect_ratio=increase,crop=${W}:${H}`;

  const opacity = clampBlurOpacity(layout.blurOpacity, BLUR_OPACITY_DEFAULT) / 100;
  const artistFontPart = opts.fontfileEscaped ? `:fontfile='${opts.fontfileEscaped}'` : "";
  const titleUsesBoldFace = Boolean(layout.titleBold && opts.titleFontfileEscaped);
  const titleFontPart = titleUsesBoldFace
    ? `:fontfile='${opts.titleFontfileEscaped}'`
    : artistFontPart;
  // Faux-bold stroke when bold is requested but no bold TTF is available (custom/curated).
  const titleFauxBold =
    layout.titleBold && !titleUsesBoldFace ? `:borderw=1:bordercolor=white@0.95` : "";
  const titleDraw = `drawtext=text='${opts.titleEscaped}'${titleFontPart}${titleFauxBold}:fontsize=${geo.titleFontSize}:fontcolor=white@0.95:x=${geo.titleX}:y=${geo.titleY}`;
  const artistDraw = opts.artistEscaped
    ? `,drawtext=text='${opts.artistEscaped}'${artistFontPart}:fontsize=${geo.artistFontSize}:fontcolor=white@0.75:x=${geo.artistX}:y=${geo.artistY}`
    : "";

  const separateBgIdx =
    typeof opts.separateBackgroundInputIndex === "number" &&
    Number.isFinite(opts.separateBackgroundInputIndex) &&
    opts.separateBackgroundInputIndex >= 0
      ? opts.separateBackgroundInputIndex
      : null;

  const parts: string[] = [];
  const bgSrc = separateBgIdx !== null ? `[${separateBgIdx}:v]` : "[bg]";
  if (separateBgIdx !== null) {
    parts.push(
      `[0:v]scale=${geo.coverMaxW}:${geo.coverMaxH}:force_original_aspect_ratio=decrease[cover]`,
    );
  } else {
    parts.push(`[0:v]split=2[bg][fg]`);
    parts.push(
      `[fg]scale=${geo.coverMaxW}:${geo.coverMaxH}:force_original_aspect_ratio=decrease[cover]`,
    );
  }

  // Fade blurred fill toward black when opacity < 100%
  if (opacity >= 0.999) {
    parts.push(`${bgSrc}${bgChain}[blurred]`);
  } else if (opacity <= 0.001) {
    parts.push(BLACK_FRAME(W, H, "blurred"));
  } else {
    const a = opacity.toFixed(3);
    const b = (1 - opacity).toFixed(3);
    parts.push(
      `${bgSrc}${bgChain}[blur_raw]`,
      BLACK_FRAME(W, H, "blk"),
      `[blur_raw][blk]blend=all_expr='A*${a}+B*${b}'[blurred]`,
    );
  }

  parts.push(
    `[blurred][cover]overlay=${geo.coverX}:${geo.coverY}[composed]`,
    `[composed]${titleDraw}${artistDraw}[laid]`,
  );

  return parts.join(";");
}

/**
 * Classic letterbox with blurred cover fill (no on-video title/artist).
 * Ends at `[laid]` — same convention as art-track for encode relabeling.
 */
export function buildClassicBlurFillFilterComplex(opts: {
  width: number;
  height: number;
  blurAmount: number;
  blurOpacity: number;
}): string {
  const { width: W, height: H } = opts;
  const blurSeg = boxblurFilterSegment(opts.blurAmount);
  const bgChain = blurSeg
    ? `scale=${W}:${H}:force_original_aspect_ratio=increase,crop=${W}:${H},${blurSeg}`
    : `scale=${W}:${H}:force_original_aspect_ratio=increase,crop=${W}:${H}`;
  const opacity = clampBlurOpacity(opts.blurOpacity, BLUR_OPACITY_DEFAULT) / 100;

  const parts: string[] = [
    `[0:v]split=2[bg][fg]`,
    `[fg]scale=${W}:${H}:force_original_aspect_ratio=decrease[cover]`,
  ];

  if (opacity >= 0.999) {
    parts.push(`[bg]${bgChain}[blurred]`);
  } else if (opacity <= 0.001) {
    parts.push(BLACK_FRAME(W, H, "blurred"));
  } else {
    const a = opacity.toFixed(3);
    const b = (1 - opacity).toFixed(3);
    parts.push(
      `[bg]${bgChain}[blur_raw]`,
      BLACK_FRAME(W, H, "blk"),
      `[blur_raw][blk]blend=all_expr='A*${a}+B*${b}'[blurred]`,
    );
  }

  parts.push(`[blurred][cover]overlay=(W-w)/2:(H-h)/2[laid]`);
  return parts.join(";");
}

export const LAYOUT_TEMPLATE_LABELS: Record<LayoutTemplate, string> = {
  COVER_LEFT_TEXT_RIGHT: "Cover left · text right",
  COVER_TOP_TEXT_BOTTOM: "Cover top · text bottom",
  COVER_RIGHT_TEXT_LEFT: "Cover right · text left",
  CENTERED_COMPACT: "Centered compact",
  LOWER_LEFT_COVER_TEXT: "Lower left · cover + text",
  LOWER_RIGHT_COVER_TEXT: "Lower right · cover + text",
};

/** Composition picker families — mirrored pairs share one tile + variant toggles. */
export type CompositionFamilyId =
  | "classic"
  | "side"
  | "top"
  | "centered"
  | "lower";

export type CompositionFamily = {
  id: CompositionFamilyId;
  /** Tile label in the composition grid. */
  label: string;
  /** Which thumb art to draw (null = classic letterbox). */
  thumb: LayoutTemplate | null;
  /** Templates in this family; length > 1 shows variant toggles when selected. */
  variants: LayoutTemplate[];
  /** Default when first selecting the family. */
  defaultTemplate: LayoutTemplate | null;
};

export const COMPOSITION_FAMILIES: readonly CompositionFamily[] = [
  {
    id: "classic",
    label: "Classic letterbox",
    thumb: null,
    variants: [],
    defaultTemplate: null,
  },
  {
    id: "side",
    label: "Cover beside text",
    thumb: "COVER_LEFT_TEXT_RIGHT",
    variants: ["COVER_LEFT_TEXT_RIGHT", "COVER_RIGHT_TEXT_LEFT"],
    defaultTemplate: "COVER_LEFT_TEXT_RIGHT",
  },
  {
    id: "top",
    label: "Cover top · text bottom",
    thumb: "COVER_TOP_TEXT_BOTTOM",
    variants: ["COVER_TOP_TEXT_BOTTOM"],
    defaultTemplate: "COVER_TOP_TEXT_BOTTOM",
  },
  {
    id: "centered",
    label: "Centered compact",
    thumb: "CENTERED_COMPACT",
    variants: ["CENTERED_COMPACT"],
    defaultTemplate: "CENTERED_COMPACT",
  },
  {
    id: "lower",
    label: "Lower corner · cover + text",
    thumb: "LOWER_LEFT_COVER_TEXT",
    variants: ["LOWER_LEFT_COVER_TEXT", "LOWER_RIGHT_COVER_TEXT"],
    defaultTemplate: "LOWER_LEFT_COVER_TEXT",
  },
] as const;

export function compositionFamilyForTemplate(
  template: LayoutTemplate | null,
): CompositionFamily {
  if (template === null) return COMPOSITION_FAMILIES[0]!;
  const found = COMPOSITION_FAMILIES.find((f) =>
    f.variants.includes(template),
  );
  return found ?? COMPOSITION_FAMILIES[0]!;
}
