import {useEffect, useState} from 'react';
import {AbsoluteFill, cancelRender, Composition, continueRender, delayRender, Img, registerRoot, staticFile, useCurrentFrame} from 'remotion';
import type {EditProps} from './model';
import {sourceTime, type CaptureManifest, type ProductionBeat} from './production';
import {Camera, ease, progress, Stage} from './visuals';

export type CompiledProduction = {
  identity: {title: string; label: string; accent: string};
  width: number;
  height: number;
  fps: number;
  durationInFrames: number;
  cueFrame: number;
  beats: (ProductionBeat & {from: number; endExclusive: number; capture: CaptureManifest})[];
};

function framePath(capture: CaptureManifest, moment: number) {
  let low = 0;
  let high = capture.frames.length - 1;
  while (low < high) {
    const middle = Math.ceil((low + high) / 2);
    if (capture.frames[middle].source_seconds <= moment) low = middle;
    else high = middle - 1;
  }
  return capture.frames[low].path;
}

const Shot = ({beat, frame, fps, accent}: {beat: CompiledProduction['beats'][number]; frame: number; fps: number; accent: string}) => {
  const local = Math.max(0, Math.min(beat.durationInFrames - 1, frame));
  const segment = beat.segments.find(candidate => candidate.fromFrame <= local && local < candidate.endFrame);
  if (!segment) throw new Error(`Missing media map for ${beat.id}/${local}`);
  const moment = sourceTime(segment, local, fps);
  const [left, top, width, height] = beat.capture.useful_region;
  const properties: EditProps = {title: beat.title, prompt: 'Captured action', result: 'Captured result', label: beat.objectId,
    accent, action: beat.motion.actionFrame, change: beat.motion.changeFrame, settle: beat.proofLocalFrame,
    hold: beat.minimumReadingFrames, focusX: beat.motion.focusX, focusY: beat.motion.focusY, zoom: beat.motion.zoom};
  const amount = beat.motion.kind === 'hold' ? 0 : beat.motion.kind === 'cut'
    ? Number(local >= beat.motion.changeFrame) : ease(progress(local, beat.motion.actionFrame, beat.motion.changeFrame));
  const omitted = beat.omissions.find(omission => omission.afterLocalFrame <= local && local < omission.afterLocalFrame + fps * 2);
  return <Stage>
    <Camera p={properties} amount={amount}>
      <div style={{position: 'absolute', inset: 0, overflow: 'hidden'}}>
        <Img src={staticFile(framePath(beat.capture, moment))} style={{position: 'absolute', maxWidth: 'none',
          left: -left * 1280 / width, top: -top * 720 / height, width: 'auto', height: 'auto',
          transform: `scale(${1280 / width}, ${720 / height})`, transformOrigin: 'top left'}}/>
      </div>
    </Camera>
    {omitted && <div style={{position: 'absolute', right: 24, bottom: 20, padding: '10px 18px', color: 'white', background: '#15251fee', fontSize: 22}}>{omitted.disclosure}</div>}
  </Stage>;
};

export const Production = ({plan}: {plan: CompiledProduction}) => {
  const frame = useCurrentFrame();
  const [handle] = useState(() => delayRender('Loading bundled fonts'));
  useEffect(() => {
    Promise.all([['DejaVuSans.ttf', '400'], ['DejaVuSans-Bold.ttf', '700']].map(async ([filename, weight]) => {
      const font = new FontFace('Study Sans', `url(${staticFile(`fonts/${filename}`)})`, {weight});
      await font.load();
      document.fonts.add(font);
    })).then(() => continueRender(handle)).catch(error => cancelRender(error));
  }, [handle]);
  const beat = plan.beats.find(candidate => candidate.from <= frame && frame < candidate.endExclusive)
    ?? [...plan.beats].reverse().find(candidate => candidate.endExclusive <= frame) ?? plan.beats[0];
  return <AbsoluteFill style={{background: '#f2f1e9'}}>
    <div style={{width: 1280, height: 720, position: 'absolute', transform: `scale(${plan.width / 1280})`, transformOrigin: 'top left'}}>
      {frame < plan.cueFrame ? <Stage>
        <div style={{position: 'absolute', left: 104, top: 230, width: 1072}}>
          <div style={{fontSize: 22, letterSpacing: 3, color: plan.identity.accent, marginBottom: 28}}>{plan.identity.label}</div>
          <div style={{fontSize: 68, fontWeight: 700, lineHeight: 1.15}}>{plan.identity.title}</div>
        </div>
      </Stage> : <Shot beat={beat} frame={frame - beat.from} fps={plan.fps} accent={plan.identity.accent}/>}
    </div>
  </AbsoluteFill>;
};

const Root = () => <Composition id="Companion" component={Production} width={1280} height={720} fps={30} durationInFrames={1}
  defaultProps={{plan: {identity: {title: 'Load a validated production packet', label: 'LOCAL STUDY', accent: '#315d52'}, width: 1280, height: 720,
    fps: 30, durationInFrames: 1, cueFrame: 1, beats: []} as CompiledProduction}}
  calculateMetadata={({props}) => ({width: props.plan.width, height: props.plan.height, fps: props.plan.fps, durationInFrames: props.plan.durationInFrames})}/>;

registerRoot(Root);
