// Maps an mvideo/v1 project to Remotion primitives. Kept close to the app's preview engine
// (TimelineEditor.vue) so what you scrub ≈ what you export. Animation + fill math come from
// mvideo-helpers.js (a mirror of @00/shared's pure helpers).
//
// Assets are served from the workspace via Remotion's publicDir (set in render.mjs), so a clip's
// workspace-relative `src` ("files/x.mp4") resolves through staticFile() — no file:// URLs.
import React from "react";
import { AbsoluteFill, Sequence, OffthreadVideo, Img, Audio, staticFile, useCurrentFrame } from "remotion";
import { sampleAnimation, fillToCss, resolveColor, audioVolume } from "./mvideo-helpers.js";

function asset(src) {
  if (!src) return "";
  return /^https?:\/\//.test(src) ? src : staticFile(String(src).replace(/^\/+/, ""));
}
function boxStyle(t) {
  const boxed = typeof t.width === "number" && typeof t.height === "number";
  return boxed
    ? { position: "absolute", left: `${(t.x || 0) * 100}%`, top: `${(t.y || 0) * 100}%`, width: `${t.width * 100}%`, height: `${t.height * 100}%`, overflow: "hidden" }
    : { position: "absolute", inset: 0, overflow: "hidden" };
}
function animStyle(anim, boxed, t) {
  const base = boxed ? "" : `scale(${t.scale ?? 1}) translate(${t.x ?? 0}px, ${t.y ?? 0}px) `;
  return {
    opacity: (t.opacity ?? 1) * anim.opacity,
    transform: `${base}translate(${anim.x}px, ${anim.y}px) scale(${anim.scale}) rotate(${anim.rotate}deg)`,
  };
}

function resolveStyle(style, branding) {
  if (style && typeof style === "object") return style;
  if (typeof style === "string" && style.startsWith("brand/") && branding?.styles) return branding.styles[style.slice(6)] || {};
  return {};
}

function TextClip({ clip, branding }) {
  const frame = useCurrentFrame();
  const s = resolveStyle(clip.style, branding);
  const anim = sampleAnimation(clip, frame, clip.to - clip.from);
  return (
    <AbsoluteFill style={{ justifyContent: "center", alignItems: "center", padding: "0 6%" }}>
      <div
        style={{
          fontSize: typeof s.size === "number" ? s.size : 48,
          color: resolveColor(s.color || "#fff", branding?.colors),
          fontFamily: branding?.fonts?.[s.font] || (typeof s.font === "string" ? s.font : "sans-serif"),
          fontWeight: s.weight ?? 700,
          textAlign: s.align || "center",
          textShadow: "0 2px 12px rgba(0,0,0,.55)",
          opacity: anim.opacity,
          transform: `translate(${anim.x}px, ${anim.y}px) scale(${anim.scale}) rotate(${anim.rotate}deg)`,
          whiteSpace: "pre-wrap",
        }}
      >
        {clip.text}
      </div>
    </AbsoluteFill>
  );
}

function VisualClip({ clip, a, branding }) {
  const frame = useCurrentFrame();
  const t = clip.transform || {};
  const boxed = typeof t.width === "number" && typeof t.height === "number";
  const anim = sampleAnimation(clip, frame, clip.to - clip.from);
  if (clip.fill) {
    return <div style={{ ...boxStyle(t), background: fillToCss(clip.fill, branding?.colors, asset), opacity: anim.opacity }} />;
  }
  const media = { width: "100%", height: "100%", objectFit: t.fit === "contain" ? "contain" : "cover", ...animStyle(anim, boxed, t) };
  const src = asset(a?.src);
  return (
    <div style={boxStyle(t)}>
      {a?.kind === "image" ? <Img src={src} style={media} /> : <OffthreadVideo src={src} trimBefore={clip.trimIn ?? 0} muted={!!clip.__muteSource} style={media} />}
    </div>
  );
}

export const Mvideo = ({ project }) => {
  const p = project || {};
  const branding = p.branding && typeof p.branding === "object" && !p.branding.$ref ? p.branding : p.__branding;
  const assets = p.assets || {};
  const tracks = p.tracks || [];
  const visual = tracks.filter((t) => t.type === "video" || t.type === "image");
  const text = tracks.filter((t) => t.type === "text");
  const audio = tracks.filter((t) => t.type === "audio");

  // Inject @font-face for any cached brand fonts so styled text renders in the right family.
  const fontCss = Object.entries((branding && branding.fontFiles) || {})
    .map(([family, rel]) => `@font-face{font-family:${JSON.stringify(family)};src:url(${JSON.stringify(asset(rel))}) format('woff2');font-display:block;}`)
    .join("\n");

  return (
    <AbsoluteFill style={{ backgroundColor: "#000" }}>
      {fontCss ? <style>{fontCss}</style> : null}
      {visual.flatMap((t) =>
        t.clips.map((clip) => (
          <Sequence key={clip.id} from={clip.from} durationInFrames={Math.max(1, clip.to - clip.from)}>
            <VisualClip clip={clip} a={assets[clip.asset]} branding={branding} />
          </Sequence>
        )),
      )}
      {text.flatMap((t) =>
        t.clips.map((clip) => (
          <Sequence key={clip.id} from={clip.from} durationInFrames={Math.max(1, clip.to - clip.from)}>
            <TextClip clip={clip} branding={branding} />
          </Sequence>
        )),
      )}
      {audio.flatMap((t) =>
        t.clips.map((clip) => {
          const a = assets[clip.asset];
          if (!a?.src) return null;
          const dur = clip.to - clip.from;
          // volume as a function of the sequence-local frame → gain (constant or automated) + edge fades
          const volume = (f) => audioVolume(clip, f, dur);
          return (
            <Sequence key={clip.id} from={clip.from} durationInFrames={Math.max(1, dur)}>
              <Audio src={asset(a.src)} trimBefore={clip.trimIn ?? 0} volume={volume} />
            </Sequence>
          );
        }),
      )}
    </AbsoluteFill>
  );
};
