"use client";

import {
  useEffect,
  useMemo,
  useState,
  type CSSProperties,
  type ChangeEvent,
} from "react";
import { getVideoAttributionText } from "@/lib/branding";
import {
  CURATED_FONTS,
  SYSTEM_FONT,
  type CuratedFontKey,
  type WatermarkFontKey,
} from "@/lib/fonts";
import {
  BLUR_AMOUNT_MAX,
  BLUR_AMOUNT_MIN,
  BLUR_OPACITY_DEFAULT,
  BLUR_OPACITY_MAX,
  BLUR_OPACITY_MIN,
  COMPOSITION_FAMILIES,
  DEFAULT_LAYOUT,
  LAYOUT_TEMPLATE_LABELS,
  TEXT_OFFSET_MAX,
  TEXT_OFFSET_MIN,
  TEXT_PADDING_MAX,
  TEXT_PADDING_MIN,
  TITLE_ARTIST_GAP_MAX,
  TITLE_ARTIST_GAP_MIN,
  compositionFamilyForTemplate,
  isLowerCornerTemplate,
  lowerCornerCoverSide,
  type LayoutSettings,
  type LayoutTemplate,
} from "@/lib/layout";
import {
  WATERMARK_WIDTH_FRACTION,
  artistFontSizeForWidth,
  curatedFontApiUrl,
  previewFontFamilyCss,
  scaleFontToPreview,
  titleFontSizeForWidth,
  watermarkFontSizeForWidth,
} from "@/lib/preview-typography";
import {
  DEFAULT_WATERMARK,
  WATERMARK_OFFSET_MAX,
  WATERMARK_OFFSET_MIN,
  WATERMARK_POSITIONS,
  WATERMARK_TEXT_MAX,
  type WatermarkMode,
  type WatermarkPosition,
  type WatermarkSettings,
} from "@/lib/watermark";

type Props = {
  locked: boolean;
  previewImageUrl: string | null;
  /** Optional custom blur-fill for lower-corner layouts. */
  previewBackgroundUrl?: string | null;
  /** On-video song title (art-track layouts). */
  songTitle: string;
  artist: string;
  /** Encode width from selected resolution (e.g. 1280). */
  encodeWidth?: number;
  layout: LayoutSettings;
  onLayoutChange: (next: LayoutSettings) => void;
  watermark: WatermarkSettings;
  onWatermarkChange: (next: WatermarkSettings) => void;
  onUploadLogo: (file: File) => Promise<string>;
  onUploadFont: (file: File) => Promise<string>;
  onUploadBackground?: (file: File) => Promise<string>;
  onClearBackground?: () => void;
  logoPreviewUrl?: string | null;
  hasCustomBackground?: boolean;
};

const WM_POSITION_LABELS: Record<WatermarkPosition, string> = {
  "top-left": "Top left",
  "top-right": "Top right",
  "bottom-left": "Bottom left",
  "bottom-right": "Bottom right",
  center: "Center",
};

const CUSTOM_PREVIEW_FAMILY = "S2VIDCustomWm";

function previewFontFamily(fontKey: WatermarkFontKey | undefined): string {
  return previewFontFamilyCss(fontKey);
}

function watermarkOverlayStyle(
  position: WatermarkPosition,
  offsetX: number,
  offsetY: number,
  sizedBox = false,
): CSSProperties {
  const base: CSSProperties = {
    position: "absolute",
    maxWidth: `${WATERMARK_WIDTH_FRACTION * 100}%`,
    pointerEvents: "none",
    zIndex: 5,
    ...(sizedBox
      ? { width: `${WATERMARK_WIDTH_FRACTION * 100}%` }
      : null),
  };
  const ox = `${offsetX}px`;
  const oy = `${offsetY}px`;
  switch (position) {
    case "top-left":
      return { ...base, top: oy, left: ox };
    case "top-right":
      return { ...base, top: oy, right: ox };
    case "bottom-left":
      return { ...base, bottom: oy, left: ox };
    case "center":
      return {
        ...base,
        top: "50%",
        left: "50%",
        transform: `translate(calc(-50% + ${offsetX}px), calc(-50% + ${offsetY}px))`,
      };
    case "bottom-right":
    default:
      return { ...base, bottom: oy, right: ox };
  }
}

function MiniThumb({
  template,
  label,
  active,
  onClick,
  disabled,
}: {
  template: LayoutTemplate | null;
  label: string;
  active: boolean;
  onClick: () => void;
  disabled: boolean;
}) {
  const isClassic = template === null;
  return (
    <button
      type="button"
      disabled={disabled}
      onClick={onClick}
      className={`overflow-hidden rounded border text-left transition-colors ${
        active
          ? "border-accent bg-accent/10"
          : "border-gray-700 bg-black/40 hover:border-gray-500"
      } disabled:cursor-not-allowed disabled:opacity-50`}
    >
      <div className="relative aspect-video w-full overflow-hidden bg-gradient-to-br from-gray-800 to-gray-950 p-1">
        {isClassic ? (
          <div className="flex h-full items-center justify-center">
            <div className="h-[70%] w-[45%] rounded-sm bg-gray-600" />
          </div>
        ) : (
          <div className="relative h-full w-full overflow-hidden bg-gray-700/50">
            <div className="absolute inset-0 scale-110 bg-gray-600/40 blur-[3px]" />
            {(template === "COVER_LEFT_TEXT_RIGHT" ||
              template === "COVER_RIGHT_TEXT_LEFT") && (
              <>
                <div className="absolute left-[6%] top-[18%] h-[64%] w-[38%] bg-gray-300" />
                <div className="absolute right-[8%] top-[38%] h-1 w-[40%] rounded bg-white/80" />
                <div className="absolute right-[8%] top-[52%] h-0.5 w-[28%] rounded bg-white/50" />
              </>
            )}
            {template === "COVER_TOP_TEXT_BOTTOM" && (
              <>
                <div className="absolute left-[8%] top-[6%] h-[52%] w-[84%] bg-gray-300" />
                <div className="absolute left-[20%] bottom-[18%] h-1 w-[60%] rounded bg-white/80" />
                <div className="absolute left-[28%] bottom-[8%] h-0.5 w-[44%] rounded bg-white/50" />
              </>
            )}
            {template === "CENTERED_COMPACT" && (
              <>
                <div className="absolute left-[32%] top-[14%] h-[42%] w-[36%] bg-gray-300" />
                <div className="absolute left-[28%] bottom-[22%] h-1 w-[44%] rounded bg-white/80" />
                <div className="absolute left-[34%] bottom-[12%] h-0.5 w-[32%] rounded bg-white/50" />
              </>
            )}
            {(template === "LOWER_LEFT_COVER_TEXT" ||
              template === "LOWER_RIGHT_COVER_TEXT") && (
              <>
                <div className="absolute bottom-[8%] left-[5%] h-[36%] w-[24%] bg-gray-300" />
                <div className="absolute bottom-[26%] left-[34%] h-1 w-[40%] rounded bg-white/80" />
                <div className="absolute bottom-[14%] left-[34%] h-0.5 w-[28%] rounded bg-white/50" />
              </>
            )}
          </div>
        )}
      </div>
      <p className="px-2 py-1.5 text-[10px] font-medium text-gray-300">{label}</p>
    </button>
  );
}

function previewCoverStyle(
  template: LayoutTemplate,
  padPct: number,
  textPadding: number,
  encodeWidth: number,
): CSSProperties {
  const p = `${padPct}%`;
  // Equal pixel inset on both axes (not % — % of width ≠ % of height on 16:9).
  const cornerEdgePx = scaleFontToPreview(textPadding, encodeWidth);
  // Assume 16:9 when only width is known; side is usually width-driven for lower corner.
  const encodeHeight = Math.round((encodeWidth * 9) / 16);
  const cornerSidePx = scaleFontToPreview(
    lowerCornerCoverSide(encodeWidth, encodeHeight, textPadding),
    encodeWidth,
  );
  switch (template) {
    case "COVER_LEFT_TEXT_RIGHT":
      return {
        position: "absolute",
        left: p,
        top: "50%",
        transform: "translateY(-50%)",
        width: "38%",
        maxHeight: "72%",
        objectFit: "contain",
      };
    case "COVER_RIGHT_TEXT_LEFT":
      return {
        position: "absolute",
        right: p,
        top: "50%",
        transform: "translateY(-50%)",
        width: "38%",
        maxHeight: "72%",
        objectFit: "contain",
      };
    case "COVER_TOP_TEXT_BOTTOM":
      return {
        position: "absolute",
        left: "50%",
        top: p,
        transform: "translateX(-50%)",
        width: `calc(100% - ${padPct * 2}%)`,
        maxHeight: "56%",
        objectFit: "contain",
      };
    case "CENTERED_COMPACT":
      return {
        position: "absolute",
        left: "50%",
        top: "16%",
        transform: "translateX(-50%)",
        width: "38%",
        maxHeight: "38%",
        objectFit: "contain",
      };
    case "LOWER_LEFT_COVER_TEXT":
      return {
        position: "absolute",
        left: cornerEdgePx,
        bottom: cornerEdgePx,
        width: cornerSidePx,
        height: cornerSidePx,
        objectFit: "contain",
      };
    case "LOWER_RIGHT_COVER_TEXT":
      return {
        position: "absolute",
        right: cornerEdgePx,
        bottom: cornerEdgePx,
        width: cornerSidePx,
        height: cornerSidePx,
        objectFit: "contain",
      };
  }
}

function previewTextStyle(
  template: LayoutTemplate,
  padPct: number,
  textOffsetX: number,
  textOffsetY: number,
  titleArtistGap: number,
  textPadding: number,
  encodeWidth: number,
): CSSProperties {
  const p = `${padPct}%`;
  const cornerEdgePx = scaleFontToPreview(textPadding, encodeWidth);
  const coverTextGapPx = scaleFontToPreview(
    Math.max(10, Math.round(textPadding * 0.55)),
    encodeWidth,
  );
  const encodeHeight = Math.round((encodeWidth * 9) / 16);
  const cornerSidePx = scaleFontToPreview(
    lowerCornerCoverSide(encodeWidth, encodeHeight, textPadding),
    encodeWidth,
  );

  const baseGap = { display: "flex", flexDirection: "column" as const, gap: `${titleArtistGap}px` };

  switch (template) {
    case "COVER_LEFT_TEXT_RIGHT":
      return {
        ...baseGap,
        position: "absolute",
        left: `calc(48% + ${textOffsetX}px)`,
        right: p,
        top: "50%",
        transform: `translateY(calc(-50% + ${textOffsetY}px))`,
        textAlign: "left",
      };
    case "COVER_RIGHT_TEXT_LEFT":
      return {
        ...baseGap,
        position: "absolute",
        left: p,
        right: `calc(48% - ${textOffsetX}px)`,
        top: "50%",
        transform: `translateY(calc(-50% + ${textOffsetY}px))`,
        textAlign: "left",
      };
    case "COVER_TOP_TEXT_BOTTOM":
      return {
        ...baseGap,
        position: "absolute",
        left: p,
        right: p,
        bottom: `calc(10% - ${textOffsetY}px)`,
        transform: textOffsetX ? `translateX(${textOffsetX}px)` : undefined,
        textAlign: "center",
        alignItems: "center",
      };
    case "CENTERED_COMPACT":
      return {
        ...baseGap,
        position: "absolute",
        left: p,
        right: p,
        top: `calc(58% + ${textOffsetY}px)`,
        transform: textOffsetX ? `translateX(${textOffsetX}px)` : undefined,
        textAlign: "center",
        alignItems: "center",
      };
    case "LOWER_LEFT_COVER_TEXT":
      return {
        ...baseGap,
        position: "absolute",
        left: cornerEdgePx + cornerSidePx + coverTextGapPx + textOffsetX,
        right: p,
        bottom: cornerEdgePx,
        height: cornerSidePx,
        justifyContent: "center",
        textAlign: "left",
        transform: textOffsetY ? `translateY(${textOffsetY}px)` : undefined,
      };
    case "LOWER_RIGHT_COVER_TEXT":
      return {
        ...baseGap,
        position: "absolute",
        left: p,
        right: cornerEdgePx + cornerSidePx + coverTextGapPx - textOffsetX,
        bottom: cornerEdgePx,
        height: cornerSidePx,
        justifyContent: "center",
        textAlign: "right",
        alignItems: "flex-end",
        transform: textOffsetY ? `translateY(${textOffsetY}px)` : undefined,
      };
  }
}

export function LayoutStudio({
  locked,
  previewImageUrl,
  previewBackgroundUrl = null,
  songTitle,
  artist,
  encodeWidth = 1280,
  layout,
  onLayoutChange,
  watermark,
  onWatermarkChange,
  onUploadLogo,
  onUploadFont,
  onUploadBackground,
  onClearBackground,
  logoPreviewUrl,
  hasCustomBackground = false,
}: Props) {
  const [logoUploading, setLogoUploading] = useState(false);
  const [logoError, setLogoError] = useState<string | null>(null);
  const [fontUploading, setFontUploading] = useState(false);
  const [fontError, setFontError] = useState<string | null>(null);
  const [customFontObjectUrl, setCustomFontObjectUrl] = useState<string | null>(null);
  const [bgUploading, setBgUploading] = useState(false);
  const [bgError, setBgError] = useState<string | null>(null);

  // Always coalesce  HMR / older session state may omit newly added fields
  const L: LayoutSettings = {
    ...DEFAULT_LAYOUT,
    ...layout,
    blurFill: layout.blurFill ?? DEFAULT_LAYOUT.blurFill,
    blurAmount: layout.blurAmount ?? DEFAULT_LAYOUT.blurAmount,
    blurOpacity: layout.blurOpacity ?? BLUR_OPACITY_DEFAULT,
    textPadding: layout.textPadding ?? DEFAULT_LAYOUT.textPadding,
    titleArtistGap: layout.titleArtistGap ?? DEFAULT_LAYOUT.titleArtistGap,
    textOffsetX: layout.textOffsetX ?? DEFAULT_LAYOUT.textOffsetX,
    textOffsetY: layout.textOffsetY ?? DEFAULT_LAYOUT.textOffsetY,
    titleBold: layout.titleBold ?? DEFAULT_LAYOUT.titleBold,
  };
  const W: WatermarkSettings = {
    ...DEFAULT_WATERMARK,
    ...watermark,
    text: watermark.text ?? "",
    offsetX: watermark.offsetX ?? DEFAULT_WATERMARK.offsetX,
    offsetY: watermark.offsetY ?? DEFAULT_WATERMARK.offsetY,
    fontKey: watermark.fontKey ?? "system",
  };

  const blurPx = useMemo(
    () => Math.round((L.blurAmount / 100) * 28),
    [L.blurAmount],
  );
  const padPct = useMemo(
    () =>
      3 +
      ((L.textPadding - TEXT_PADDING_MIN) / (TEXT_PADDING_MAX - TEXT_PADDING_MIN)) * 5,
    [L.textPadding],
  );

  const wmOverlay = useMemo(
    () =>
      watermarkOverlayStyle(
        W.position,
        W.offsetX,
        W.offsetY,
        W.mode === "default" || W.mode === "logo",
      ),
    [W.position, W.offsetX, W.offsetY, W.mode],
  );

  const textFontStyle = useMemo(
    () => ({ fontFamily: previewFontFamily(W.fontKey) }),
    [W.fontKey],
  );

  const titlePreviewPx = useMemo(
    () => scaleFontToPreview(titleFontSizeForWidth(encodeWidth), encodeWidth),
    [encodeWidth],
  );
  const artistPreviewPx = useMemo(
    () => scaleFontToPreview(artistFontSizeForWidth(encodeWidth), encodeWidth),
    [encodeWidth],
  );
  const wmTextPreviewPx = useMemo(
    () => scaleFontToPreview(watermarkFontSizeForWidth(encodeWidth), encodeWidth),
    [encodeWidth],
  );

  useEffect(() => {
    const styleId = "s2vid-preview-curated-fonts";
    let el = document.getElementById(styleId) as HTMLStyleElement | null;
    if (!el) {
      el = document.createElement("style");
      el.id = styleId;
      document.head.appendChild(el);
    }
    el.textContent = [
      `@font-face {
  font-family: '${SYSTEM_FONT.previewFamily}';
  src: url('${curatedFontApiUrl(SYSTEM_FONT.key)}') format('truetype');
  font-weight: 400;
  font-display: swap;
}`,
      `@font-face {
  font-family: '${SYSTEM_FONT.previewFamily}';
  src: url('${curatedFontApiUrl(SYSTEM_FONT.key)}?weight=bold') format('truetype');
  font-weight: 600;
  font-display: swap;
}`,
      `@font-face {
  font-family: '${SYSTEM_FONT.previewFamily}';
  src: url('${curatedFontApiUrl(SYSTEM_FONT.key)}?weight=bold') format('truetype');
  font-weight: 700;
  font-display: swap;
}`,
      ...CURATED_FONTS.map(
        (f) => `@font-face {
  font-family: 'S2VIDPreview-${f.key}';
  src: url('${curatedFontApiUrl(f.key)}') format('truetype');
  font-weight: 400;
  font-display: swap;
}`,
      ),
    ].join("\n");
  }, []);

  useEffect(() => {
    if (!customFontObjectUrl) return;
    const styleId = "s2vid-watermark-custom-font";
    let el = document.getElementById(styleId) as HTMLStyleElement | null;
    if (!el) {
      el = document.createElement("style");
      el.id = styleId;
      document.head.appendChild(el);
    }
    el.textContent = `
@font-face {
  font-family: '${CUSTOM_PREVIEW_FAMILY}';
  src: url('${customFontObjectUrl}');
  font-display: swap;
}`;
  }, [customFontObjectUrl]);

  useEffect(() => {
    return () => {
      if (customFontObjectUrl) URL.revokeObjectURL(customFontObjectUrl);
    };
  }, [customFontObjectUrl]);

  function patchLayout(partial: Partial<LayoutSettings>) {
    if (locked) return;
    onLayoutChange({ ...DEFAULT_LAYOUT, ...layout, ...partial });
  }

  function patchWm(partial: Partial<WatermarkSettings>) {
    if (locked) return;
    onWatermarkChange({ ...DEFAULT_WATERMARK, ...watermark, ...partial });
  }

  async function handleLogo(e: ChangeEvent<HTMLInputElement>) {
    const file = e.target.files?.[0];
    e.target.value = "";
    if (!file || locked) return;
    setLogoError(null);
    setLogoUploading(true);
    try {
      const path = await onUploadLogo(file);
      patchWm({ mode: "logo", logoPath: path });
    } catch (err) {
      setLogoError(err instanceof Error ? err.message : "Logo upload failed");
    } finally {
      setLogoUploading(false);
    }
  }

  async function handleFont(e: ChangeEvent<HTMLInputElement>) {
    const file = e.target.files?.[0];
    e.target.value = "";
    if (!file || locked) return;
    setFontError(null);
    setFontUploading(true);
    try {
      const objectUrl = URL.createObjectURL(file);
      if (customFontObjectUrl) URL.revokeObjectURL(customFontObjectUrl);
      setCustomFontObjectUrl(objectUrl);
      const path = await onUploadFont(file);
      patchWm({ fontKey: "custom", fontPath: path });
    } catch (err) {
      setFontError(err instanceof Error ? err.message : "Font upload failed");
    } finally {
      setFontUploading(false);
    }
  }

  async function handleBackground(e: ChangeEvent<HTMLInputElement>) {
    const file = e.target.files?.[0];
    e.target.value = "";
    if (!file || locked || !onUploadBackground) return;
    setBgError(null);
    setBgUploading(true);
    try {
      await onUploadBackground(file);
    } catch (err) {
      setBgError(err instanceof Error ? err.message : "Background upload failed");
    } finally {
      setBgUploading(false);
    }
  }

  function setFontKey(key: WatermarkFontKey) {
    if (key === "custom") {
      patchWm({ fontKey: "custom", fontPath: watermark.fontPath ?? null });
      return;
    }
    patchWm({ fontKey: key, fontPath: null });
  }

  const artTrack = Boolean(L.template);
  const lowerCorner = isLowerCornerTemplate(L.template);
  const blurPreviewUrl = previewBackgroundUrl || previewImageUrl;

  return (
    <div
      className={`relative rounded-lg border border-gray-700 bg-surface p-6 ${
        locked ? "opacity-80" : ""
      }`}
    >
      <div className="mb-4 flex flex-wrap items-center gap-2">
        <h3 className="text-lg font-medium text-white">Video layout</h3>
      </div>

      {/* Single live preview: art-track + watermark */}
      <div
        className="relative mx-auto aspect-video w-full max-w-xl overflow-hidden rounded border border-gray-800 bg-black"
        aria-hidden={locked}
      >
        {previewImageUrl ? (
          <>
            {artTrack ? (
              <>
                <div className="absolute inset-0 bg-black" />
                {/* eslint-disable-next-line @next/next/no-img-element */}
                <img
                  src={blurPreviewUrl!}
                  alt=""
                  className="absolute inset-0 h-full w-full object-cover"
                  style={{
                    filter: L.blurAmount > 0 ? `blur(${blurPx}px)` : undefined,
                    transform: "scale(1.15)",
                    opacity: L.blurOpacity / 100,
                  }}
                />
                {/* eslint-disable-next-line @next/next/no-img-element */}
                <img
                  src={previewImageUrl}
                  alt=""
                  style={previewCoverStyle(
                    L.template!,
                    padPct,
                    L.textPadding,
                    encodeWidth,
                  )}
                  className="pointer-events-none"
                />
                <div
                  style={previewTextStyle(
                    L.template!,
                    padPct,
                    L.textOffsetX,
                    L.textOffsetY,
                    L.titleArtistGap,
                    L.textPadding,
                    encodeWidth,
                  )}
                >
                  <p
                    className={`max-w-full truncate text-white drop-shadow ${
                      L.titleBold ? "font-semibold" : "font-normal"
                    }`}
                    style={{
                      ...textFontStyle,
                      fontWeight: L.titleBold ? 600 : 400,
                      fontSize: `${titlePreviewPx}px`,
                      lineHeight: 1.15,
                    }}
                  >
                    {songTitle.trim() || "Track title"}
                  </p>
                  <p
                    className="max-w-full truncate text-white/75 drop-shadow"
                    style={{
                      ...textFontStyle,
                      fontSize: `${artistPreviewPx}px`,
                      lineHeight: 1.15,
                    }}
                  >
                    {artist.trim() || "Artist"}
                  </p>
                </div>
              </>
            ) : (
              <div className="absolute inset-0 bg-black">
                {L.blurFill && (
                  // eslint-disable-next-line @next/next/no-img-element
                  <img
                    src={previewImageUrl}
                    alt=""
                    className="absolute inset-0 h-full w-full object-cover"
                    style={{
                      filter: L.blurAmount > 0 ? `blur(${blurPx}px)` : undefined,
                      transform: "scale(1.15)",
                      opacity: L.blurOpacity / 100,
                    }}
                  />
                )}
                <div className="absolute inset-0 flex items-center justify-center">
                  {/* eslint-disable-next-line @next/next/no-img-element */}
                  <img
                    src={previewImageUrl}
                    alt=""
                    className="max-h-full max-w-full object-contain"
                  />
                </div>
              </div>
            )}

            {W.mode !== "none" && (
              <div style={wmOverlay}>
                {W.mode === "default" ? (
                  // eslint-disable-next-line @next/next/no-img-element
                  <img
                    src="/api/branding/watermark"
                    alt=""
                    className="h-auto w-full object-contain"
                  />
                ) : W.mode === "logo" && logoPreviewUrl ? (
                  // eslint-disable-next-line @next/next/no-img-element
                  <img
                    src={logoPreviewUrl}
                    alt=""
                    className="h-auto w-full object-contain"
                  />
                ) : W.mode === "text" ? (
                  <span
                    className="font-medium text-white/90 drop-shadow"
                    style={{
                      ...textFontStyle,
                      fontSize: `${wmTextPreviewPx}px`,
                    }}
                  >
                    {W.text?.trim()
                      ? W.text.trim().slice(0, WATERMARK_TEXT_MAX)
                      : getVideoAttributionText()}
                  </span>
                ) : null}
              </div>
            )}
          </>
        ) : (
          <div className="flex h-full items-center justify-center text-sm text-gray-600">
            Upload a cover image to preview
          </div>
        )}
      </div>

      {/* Art-track templates */}
      <p className="mb-2 mt-5 text-sm font-medium text-gray-300">Composition</p>
      <div className="grid grid-cols-2 gap-2 sm:grid-cols-3 lg:grid-cols-5">
        {COMPOSITION_FAMILIES.map((family) => {
          const active =
            family.id === "classic"
              ? L.template === null
              : family.variants.includes(L.template as LayoutTemplate);
          return (
            <MiniThumb
              key={family.id}
              template={family.thumb}
              label={family.label}
              active={active}
              disabled={locked}
              onClick={() => {
                if (family.id === "classic") {
                  patchLayout({ template: null });
                  return;
                }
                if (
                  L.template &&
                  family.variants.includes(L.template as LayoutTemplate)
                ) {
                  return;
                }
                patchLayout({ template: family.defaultTemplate, blurFill: false });
              }}
            />
          );
        })}
      </div>
      {(() => {
        const family = compositionFamilyForTemplate(L.template);
        if (family.variants.length < 2) return null;
        return (
          <div className="mt-2 flex flex-wrap gap-2">
            {family.variants.map((variant) => (
              <button
                key={variant}
                type="button"
                disabled={locked}
                onClick={() => patchLayout({ template: variant })}
                className={`rounded border px-2.5 py-1.5 text-[11px] font-medium transition-colors ${
                  L.template === variant
                    ? "border-accent bg-accent/15 text-white"
                    : "border-gray-700 bg-black/40 text-gray-300 hover:border-gray-500"
                } disabled:cursor-not-allowed disabled:opacity-50`}
              >
                {LAYOUT_TEMPLATE_LABELS[variant]}
              </button>
            ))}
          </div>
        );
      })()}

      {!artTrack && (
        <label className="mt-3 flex cursor-pointer items-start gap-2 text-sm text-gray-300">
          <input
            type="checkbox"
            className="mt-0.5"
            disabled={locked}
            checked={L.blurFill}
            onChange={(e) => patchLayout({ blurFill: e.target.checked })}
          />
          <span>
            <span className="font-medium text-gray-200">Blurred cover background</span>
            <span className="mt-0.5 block text-xs text-gray-500">
              Fill letterbox bars with a blurred cover instead of black. Adjust blur and opacity
              below when enabled.
            </span>
          </span>
        </label>
      )}

      {lowerCorner && (
        <div className="mt-3 rounded border border-gray-700 bg-black/30 p-3">
          <p className="mb-2 text-sm font-medium text-gray-300">Background image</p>
          <p className="mb-2 text-xs text-gray-500">
            Optional. Used as the blurred full-frame fill; the cover stays the sharp corner square.
            Leave empty to blur the cover itself.
          </p>
          <div className="flex flex-wrap items-center gap-2">
            <label
              className={`cursor-pointer rounded border border-gray-600 bg-surface-dark px-3 py-1.5 text-xs text-gray-200 hover:border-gray-400 ${
                locked || bgUploading ? "pointer-events-none opacity-40" : ""
              }`}
            >
              {bgUploading ? "Uploading…" : hasCustomBackground ? "Replace image" : "Upload image"}
              <input
                type="file"
                accept="image/*"
                className="hidden"
                disabled={locked || bgUploading || !onUploadBackground}
                onChange={handleBackground}
              />
            </label>
            {hasCustomBackground && (
              <button
                type="button"
                disabled={locked}
                onClick={() => onClearBackground?.()}
                className="rounded border border-gray-700 px-3 py-1.5 text-xs text-gray-400 hover:border-gray-500 hover:text-gray-200 disabled:opacity-40"
              >
                Use cover as background
              </button>
            )}
          </div>
          {bgError && <p className="mt-2 text-xs text-red-400">{bgError}</p>}
        </div>
      )}

      <div className="mt-4 grid gap-3 sm:grid-cols-2">
        <label className="text-sm text-gray-400">
          Background blur ({L.blurAmount}%)
          <input
            type="range"
            min={BLUR_AMOUNT_MIN}
            max={BLUR_AMOUNT_MAX}
            disabled={locked || !(artTrack || L.blurFill)}
            value={L.blurAmount}
            onChange={(e) => patchLayout({ blurAmount: Number(e.target.value) })}
            className="mt-1 w-full disabled:opacity-40"
          />
        </label>
        <label className="text-sm text-gray-400">
          Background opacity ({L.blurOpacity}%)
          <input
            type="range"
            min={BLUR_OPACITY_MIN}
            max={BLUR_OPACITY_MAX}
            disabled={locked || !(artTrack || L.blurFill)}
            value={L.blurOpacity}
            onChange={(e) => patchLayout({ blurOpacity: Number(e.target.value) })}
            className="mt-1 w-full disabled:opacity-40"
          />
        </label>
        <label className="text-sm text-gray-400">
          Cover / edge padding ({L.textPadding}px)
          <input
            type="range"
            min={TEXT_PADDING_MIN}
            max={TEXT_PADDING_MAX}
            disabled={locked || !artTrack}
            value={L.textPadding}
            onChange={(e) => patchLayout({ textPadding: Number(e.target.value) })}
            className="mt-1 w-full disabled:opacity-40"
          />
        </label>
        <label className="text-sm text-gray-400">
          Title ↔ artist gap ({L.titleArtistGap}px)
          <input
            type="range"
            min={TITLE_ARTIST_GAP_MIN}
            max={TITLE_ARTIST_GAP_MAX}
            disabled={locked || !artTrack}
            value={L.titleArtistGap}
            onChange={(e) =>
              patchLayout({ titleArtistGap: Number(e.target.value) })
            }
            className="mt-1 w-full disabled:opacity-40"
          />
        </label>
        <label className="text-sm text-gray-400">
          Text horizontal ({L.textOffsetX}px)
          <input
            type="range"
            min={TEXT_OFFSET_MIN}
            max={TEXT_OFFSET_MAX}
            disabled={locked || !artTrack}
            value={L.textOffsetX}
            onChange={(e) => patchLayout({ textOffsetX: Number(e.target.value) })}
            className="mt-1 w-full disabled:opacity-40"
          />
        </label>
        <label className="text-sm text-gray-400 sm:col-span-2">
          Text vertical ({L.textOffsetY}px)
          <input
            type="range"
            min={TEXT_OFFSET_MIN}
            max={TEXT_OFFSET_MAX}
            disabled={locked || !artTrack}
            value={L.textOffsetY}
            onChange={(e) => patchLayout({ textOffsetY: Number(e.target.value) })}
            className="mt-1 w-full disabled:opacity-40"
          />
        </label>
        <label
          className={`flex items-center gap-2 text-sm text-gray-300 sm:col-span-2 ${
            locked || !artTrack ? "opacity-40" : ""
          }`}
        >
          <input
            type="checkbox"
            className="rounded border-gray-600 bg-surface-dark text-accent focus:ring-accent"
            disabled={locked || !artTrack}
            checked={L.titleBold}
            onChange={(e) => patchLayout({ titleBold: e.target.checked })}
          />
          Bold song title
        </label>
      </div>

      {/* Typography (matches FFmpeg art-track + watermark text fonts) */}
      <div className="mt-6 border-t border-gray-800 pt-5">
        <p className="mb-3 text-sm font-medium text-gray-300">Typography</p>
        <label className="block text-sm text-gray-400">
          Font
          <select
            disabled={locked}
            value={W.fontKey ?? "system"}
            onChange={(e) => setFontKey(e.target.value as WatermarkFontKey)}
            className="input-field mt-1"
          >
            <option value="system">{SYSTEM_FONT.label}</option>
            {CURATED_FONTS.map((f) => (
              <option key={f.key} value={f.key}>
                {f.label}
              </option>
            ))}
            <option value="custom">Custom upload (.ttf / .otf)</option>
          </select>
        </label>
        {W.fontKey === "custom" && (
          <div className="mt-3">
            <label className="mb-1 block text-sm text-gray-400">
              Upload font (.ttf or .otf, max 10 MB)
            </label>
            <input
              type="file"
              accept=".ttf,.otf,font/ttf,font/otf"
              disabled={locked || fontUploading}
              onChange={(e) => void handleFont(e)}
              className="block w-full text-sm text-gray-300 file:mr-4 file:rounded file:border-0 file:bg-accent file:px-4 file:py-2 file:text-sm file:text-white"
            />
            {fontUploading && (
              <p className="mt-1 text-xs text-yellow-400">Uploading font…</p>
            )}
            {fontError && <p className="mt-1 text-xs text-red-400">{fontError}</p>}
          </div>
        )}
        {(W.fontKey === "custom" ||
          (W.fontKey &&
            W.fontKey !== "system" &&
            CURATED_FONTS.some((f) => f.key === (W.fontKey as CuratedFontKey)))) && (
          <p
            className="mt-2 text-xs text-gray-500"
            style={{ ...textFontStyle, fontSize: `${scaleFontToPreview(16, encodeWidth)}px` }}
          >
            Preview: The quick brown fox jumps over the lazy dog
          </p>
        )}
      </div>

      {/* Watermark section */}
      <div className="mt-6 border-t border-gray-800 pt-5">
        <p className="mb-3 text-sm font-medium text-gray-300">Watermark</p>
        <div className="flex flex-wrap gap-2">
          {(
            [
              ["none", "No watermark"],
              ["default", "Songs2VID badge"],
              ["text", "Custom text"],
              ["logo", "PNG logo"],
            ] as const
          ).map(([mode, label]) => (
            <button
              key={mode}
              type="button"
              disabled={locked}
              onClick={() => patchWm({ mode: mode as WatermarkMode })}
              className={`rounded border px-3 py-1.5 text-xs font-medium transition-colors ${
                W.mode === mode
                  ? "border-accent bg-accent/15 text-accent"
                  : "border-gray-700 text-gray-400 hover:border-gray-500"
              } disabled:cursor-not-allowed`}
            >
              {label}
            </button>
          ))}
        </div>

        {W.mode === "text" && (
          <div className="mt-3 space-y-3">
            <label className="block text-sm text-gray-400">
              Watermark text
              <input
                type="text"
                maxLength={WATERMARK_TEXT_MAX}
                disabled={locked}
                value={W.text ?? ""}
                onChange={(e) => patchWm({ text: e.target.value })}
                className="input-field mt-1"
                placeholder="Your brand name"
              />
            </label>
          </div>
        )}

        {W.mode === "logo" && (
          <div className="mt-3">
            <label className="mb-1 block text-sm text-gray-400">PNG logo</label>
            <input
              type="file"
              accept="image/png,.png"
              disabled={locked || logoUploading}
              onChange={(e) => void handleLogo(e)}
              className="block w-full text-sm text-gray-300 file:mr-4 file:rounded file:border-0 file:bg-accent file:px-4 file:py-2 file:text-sm file:text-white"
            />
            {logoUploading && (
              <p className="mt-1 text-xs text-yellow-400">Uploading logo…</p>
            )}
            {logoError && <p className="mt-1 text-xs text-red-400">{logoError}</p>}
          </div>
        )}

        <div className="mt-4">
          <p className="mb-2 text-sm text-gray-400">Watermark position</p>
          <div className="grid grid-cols-3 gap-2 sm:grid-cols-5">
            {WATERMARK_POSITIONS.map((pos) => (
              <button
                key={pos}
                type="button"
                disabled={locked || W.mode === "none"}
                onClick={() => patchWm({ position: pos })}
                className={`rounded border px-2 py-2 text-[11px] font-medium ${
                  W.position === pos
                    ? "border-accent bg-accent/15 text-accent"
                    : "border-gray-700 text-gray-400"
                } disabled:opacity-40`}
              >
                {WM_POSITION_LABELS[pos]}
              </button>
            ))}
          </div>
        </div>

        <div className="mt-3 grid gap-3 sm:grid-cols-2">
          <label className="text-sm text-gray-400">
            Watermark offset X ({W.offsetX}px)
            <input
              type="range"
              min={WATERMARK_OFFSET_MIN}
              max={WATERMARK_OFFSET_MAX}
              disabled={locked || W.mode === "none"}
              value={W.offsetX}
              onChange={(e) => patchWm({ offsetX: Number(e.target.value) })}
              className="mt-1 w-full"
            />
          </label>
          <label className="text-sm text-gray-400">
            Watermark offset Y ({W.offsetY}px)
            <input
              type="range"
              min={WATERMARK_OFFSET_MIN}
              max={WATERMARK_OFFSET_MAX}
              disabled={locked || W.mode === "none"}
              value={W.offsetY}
              onChange={(e) => patchWm({ offsetY: Number(e.target.value) })}
              className="mt-1 w-full"
            />
          </label>
        </div>
      </div>
    </div>
  );
}
