import { applyLibraryToClip, applyLibraryToTrack } from '../components/timeline/libraryDropActions';
import { makeDraft } from '../editor/store';
import type { MediaAsset, TimelineState } from '../editor/types';
import type { LibraryDragPayload } from '../library/drag';
import { pluginResourceItems } from '../library/pluginResources';
import { pluginTemplates } from '../library/pluginTemplateCatalog';
import { docFromTimeline } from '../persist/projectStore';
import {
  HOVER_DURATION_MS,
  HOVER_HOLD_FRACTION,
} from '../gl/transitionThumb';
import {
  registerPack,
  unregisterPack,
  type InstalledPack,
} from './store';
import type { PluginPack } from './types';

export type ResourcePreviewCategory = 'mg' | 'transition' | 'fx' | 'zoom' | 'lut';

const FPS = 30;
const FRAMES = FPS * 5;
const TRACK = 'preview-video';
const TRANSITION_PREVIEW_FRAMES = Math.round(FPS * HOVER_DURATION_MS / 1000);
const TRANSITION_EFFECT_FRAMES = Math.round(
  TRANSITION_PREVIEW_FRAMES * (1 - 2 * HOVER_HOLD_FRACTION),
);

function emptyTimeline(): TimelineState {
  return {
    fps: FPS,
    width: 1920,
    height: 1080,
    fit: 'cover',
    items: [],
    selectedId: null,
    trackOrder: [TRACK],
    tracks: { [TRACK]: { kind: 'video' } },
  };
}

function asInstalledPack(pack: PluginPack): InstalledPack {
  return {
    ...pack,
    enabled: true,
    installedAt: 0,
    cubeUrls: Object.fromEntries(
      pack.items
        .flatMap((item) => item.type === 'lut' && item.cube
          ? [[item.id, `data:text/plain;charset=utf-8,${encodeURIComponent(item.cube)}`]]
          : []),
    ),
  };
}

function coverAsset(
  id: string,
  src: string,
  durationInFrames: number,
): MediaAsset {
  return {
    id,
    name: 'Preview source',
    kind: 'image',
    src,
    durationInFrames,
    width: 1920,
    height: 1080,
  };
}

function resourcePayload(
  pack: InstalledPack,
  category: Exclude<ResourcePreviewCategory, 'mg'>,
): LibraryDragPayload {
  const resource = pluginResourceItems([pack], category)[0];
  if (!resource) throw new Error(`package has no ${category} item`);
  return {
    v: 1,
    kind: category,
    id: resource.id,
    name: resource.name,
    ...(resource.data === undefined ? {} : { data: resource.data }),
  };
}

function buildMg(pack: InstalledPack): TimelineState {
  const draft = makeDraft(docFromTimeline(emptyTimeline()));
  const template = pluginTemplates([pack])[0];
  if (!template) throw new Error('package has no mg-template item');
  const applied = applyLibraryToTrack(
    {
      state: draft.getState(),
      getState: () => draft.getState(),
      getAssets: () => draft.getState().assets ?? [],
      commands: draft.commands,
      notice: () => undefined,
    },
    { v: 1, kind: 'template', id: template.id, name: template.name, data: template },
    TRACK,
    0,
    false,
  );
  if (!applied) throw new Error('MG template was rejected by OpenChatCut');
  return draft.getState();
}

function addVisualClips(
  draft: ReturnType<typeof makeDraft>,
  category: Exclude<ResourcePreviewCategory, 'mg'>,
  coverDataUrl: string,
  targetDataUrl?: string,
): string {
  const outgoingFrames = category === 'transition'
    ? Math.ceil(TRANSITION_PREVIEW_FRAMES / 2)
    : FRAMES;
  const asset = coverAsset('preview-cover', coverDataUrl, outgoingFrames);
  draft.commands.addAsset(asset);
  const firstId = draft.commands.addMediaItem(asset, { track: TRACK, startFrame: 0 });
  if (category !== 'transition') return firstId;
  const target = coverAsset(
    'preview-target',
    targetDataUrl || coverDataUrl,
    TRANSITION_PREVIEW_FRAMES - outgoingFrames,
  );
  draft.commands.addAsset(target);
  const incomingId = draft.commands.addMediaItem(target, {
    track: TRACK,
    startFrame: outgoingFrames,
  });
  if (!targetDataUrl) {
    draft.commands.setItemTransform(incomingId, { scale: 1.08 });
    draft.commands.setItemFilters(incomingId, { brightness: 0.92, saturate: 0.82 });
  }
  return incomingId;
}

function buildVisual(
  pack: InstalledPack,
  category: Exclude<ResourcePreviewCategory, 'mg'>,
  coverDataUrl: string,
  targetDataUrl?: string,
): TimelineState {
  const draft = makeDraft(docFromTimeline(emptyTimeline()));
  const itemId = addVisualClips(draft, category, coverDataUrl, targetDataUrl);
  const state = draft.getState();
  const item = state.items.find((candidate) => candidate.id === itemId);
  if (!item) throw new Error('preview clip was not created');
  let notice = '';
  const applied = applyLibraryToClip(
    {
      state,
      getState: () => draft.getState(),
      getAssets: () => draft.getState().assets ?? [],
      commands: draft.commands,
      notice: (message) => { notice = message; },
    },
    resourcePayload(pack, category),
    item,
  );
  if (!applied) throw new Error(notice || `${category} was rejected by OpenChatCut`);
  if (category === 'transition') {
    const transition = draft.getState().transitions?.find(
      (candidate) => candidate.incomingItemId === itemId,
    );
    if (!transition) throw new Error('transition preview was not created');
    draft.commands.setTransition(transition.id, {
      durationInFrames: TRANSITION_EFFECT_FRAMES,
    });
  }
  return draft.getState();
}

export async function buildResourcePreviewState(
  sourcePack: PluginPack,
  category: ResourcePreviewCategory,
  coverDataUrl: string,
  targetDataUrl?: string,
): Promise<TimelineState> {
  const pack = asInstalledPack(sourcePack);
  await registerPack(pack);
  try {
    return category === 'mg'
      ? buildMg(pack)
      : buildVisual(pack, category, coverDataUrl, targetDataUrl);
  } finally {
    await unregisterPack(pack);
  }
}