import React, { useEffect, useRef, useState } from 'react';

import { computeTimeSec } from '../../main/video/videoPlaybackStore';
import type { SessionState } from '../../shared/ipc/contracts';
import { USERS_BRANCH_FEATURES_ENABLED } from '../../shared/features/usersBranchFeatures';

import { ExplosionVideoOverlay } from './effects/ExplosionVideoOverlay';
import { PixiEffectsOverlay } from './effects/PxiEffectsOverlay';
import { SceneDarknessOverlay } from './effects/SceneDarknessOverlay';
import { useEffectsState } from './effects/useEffectsState';
import { useSceneDarknessState } from './effects/useSceneDarknessState';
import { DEFAULT_MATERIALS_OVERLAY_LAYOUT, DEFAULT_NPCS_OVERLAY_LAYOUT } from '../../shared/types';
import { SceneGridOverlay } from './grid/SceneGridOverlay';
import { MaterialLegendPanel } from './materials/MaterialLegendPanel';
import { MaterialOverlay } from './materials/MaterialOverlay';
import { useMaterialsOverlayState } from './materials/useMaterialsOverlayState';
import { NpcsSceneOverlay } from './npcs/NpcsSceneOverlay';
import { useNpcsOverlayState } from './npcs/useNpcsOverlayState';
import { SceneNpcTokensOverlay } from './playerToken/SceneNpcTokensOverlay';
import { ScenePlayerTokensOverlay } from './playerToken/ScenePlayerTokensOverlay';
import { useAppPlayers } from './playerToken/useAppPlayers';
import { useSceneNpcTokensSession } from './playerToken/useSceneNpcTokensSession';
import { useScenePlayerTokensSession } from './playerToken/useScenePlayerTokensSession';
import { SceneOverlayHost } from './sceneOverlay/SceneOverlayHost';
import { useSceneViewState } from './sceneView/useSceneViewState';
import { SceneTokensOverlay } from './tokens/SceneTokensOverlay';
import { TokenPathsOverlay } from './tokens/TokenPathsOverlay';
import { useAppTokens } from './tokens/useAppTokens';
import { useSceneTokensSession } from './tokens/useSceneTokensSession';
import { useTokenPathLivePoses } from './tokens/useTokenPathLivePoses';
import { useTokenPathSession } from './tokens/useTokenPathSession';
import { SceneTrapsOverlay } from './traps/SceneTrapsOverlay';
import { useSceneTrapsState } from './traps/useSceneTrapsState';
import styles from './PresentationView.module.css';
import { ContainedVideo } from './ContainedVideo';
import { RotatedImage } from './RotatedImage';
import { useAssetUrl } from './useAssetImageUrl';
import { useVideoPlaybackState } from './video/useVideoPlaybackState';
import {
  DEFAULT_NPC_TOKEN_SESSION_SCALE,
  listVisibleSceneNpcTokens,
} from '../../shared/types/appPlayers';

export type PresentationViewProps = {
  session: SessionState | null;
  /** Если true — показываем укороченный заголовок/оверлей для предпросмотра. */
  compact?: boolean;
  showTitle?: boolean;
  showEffects?: boolean;
};

export function PresentationView({
  session,
  compact = false,
  showTitle = true,
  showEffects = true,
}: PresentationViewProps) {
  const [fxState] = useEffectsState();
  const [sdState] = useSceneDarknessState();
  const [sceneTraps] = useSceneTrapsState();
  const [sceneView] = useSceneViewState();
  const [materialsOverlay] = useMaterialsOverlayState();
  const [npcsOverlay] = useNpcsOverlayState();
  const appTokens = useAppTokens();
  const appPlayersResult = useAppPlayers();
  const appPlayers = USERS_BRANCH_FEATURES_ENABLED ? appPlayersResult.players : [];
  const [sceneTokensSession] = useSceneTokensSession();
  const [sceneNpcTokensSession] = useSceneNpcTokensSession();
  const [scenePlayerTokensSession] = useScenePlayerTokensSession();
  const [tokenPathSession, tokenPathApi] = useTokenPathSession();
  const [vp] = useVideoPlaybackState();
  const videoElRef = useRef<HTMLVideoElement | null>(null);
  const [contentRect, setContentRect] = React.useState<{ x: number; y: number; w: number; h: number } | null>(
    null,
  );
  const scene =
    session?.project && session.currentSceneId ? session.project.scenes[session.currentSceneId] : undefined;
  const project = session?.project;
  const visibleNpcTokens = listVisibleSceneNpcTokens(
    scene?.npcTokens,
    sceneNpcTokensSession.spawned,
    session?.currentSceneId,
  );
  const pathLivePoses = useTokenPathLivePoses({
    tokens: scene?.tokens ?? [],
    npcTokens: visibleNpcTokens,
    pathSession: tokenPathSession,
    enabled: Boolean(scene),
    onMarkDone: (kind, placementId) => {
      void tokenPathApi.dispatch({ kind: 'markDone', target: { kind, placementId } });
    },
  });
  const activeMaterialItems =
    project && (materialsOverlay?.activeMaterialIds?.length ?? 0) > 0
      ? (materialsOverlay?.activeMaterialIds ?? [])
          .map((id) => {
            const material = (project.materials ?? []).find((m) => m.id === id);
            if (!material) return null;
            return {
              material,
              layout: materialsOverlay?.layouts[id] ?? DEFAULT_MATERIALS_OVERLAY_LAYOUT,
              legendLayout: materialsOverlay?.legendLayouts[id] ?? DEFAULT_MATERIALS_OVERLAY_LAYOUT,
            };
          })
          .filter((x): x is NonNullable<typeof x> => x !== null)
      : [];
  const activeNpcItems =
    project && (npcsOverlay?.activeNpcIds?.length ?? 0) > 0
      ? (npcsOverlay?.activeNpcIds ?? [])
          .map((id) => {
            const npc = (project.npcs ?? []).find((n) => n.id === id);
            if (!npc) return null;
            return {
              npcId: npc.id,
              assetId: npc.avatarAssetId,
              layout: npcsOverlay?.layouts[id] ?? DEFAULT_NPCS_OVERLAY_LAYOUT,
            };
          })
          .filter((x): x is NonNullable<typeof x> => x !== null)
      : [];
  const originalUrl = useAssetUrl(scene?.previewAssetId ?? null);
  const thumbUrl = useAssetUrl(scene?.previewThumbAssetId ?? null);
  const [shownImageUrl, setShownImageUrl] = useState<string | null>(null);
  const rot = scene?.previewRotationDeg ?? 0;

  useEffect(() => {
    if (!scene) {
      setShownImageUrl(null);
      return;
    }
    if (scene.previewAssetType !== 'image') {
      setShownImageUrl(null);
      return;
    }
    // Show thumbnail instantly (if exists) to avoid stutter on slide switch, then swap to original when loaded.
    setShownImageUrl(thumbUrl ?? originalUrl);
    if (!thumbUrl || !originalUrl || thumbUrl === originalUrl) return;
    let cancelled = false;
    const img = new Image();
    img.decoding = 'async';
    img.onload = () => {
      if (cancelled) return;
      setShownImageUrl(originalUrl);
    };
    img.onerror = () => {
      // keep thumbnail
    };
    img.src = originalUrl;
    return () => {
      cancelled = true;
    };
  }, [originalUrl, scene, thumbUrl]);

  useEffect(() => {
    const el = videoElRef.current;
    if (!el) return;
    if (!vp) return;
    if (!scene?.previewAssetId) return;
    if (scene.previewAssetType !== 'video') return;
    if (vp.targetAssetId !== scene.previewAssetId) return;

    el.playbackRate = vp.playbackRate;
    const desired = computeTimeSec(vp, vp.serverNowMs);
    if (Number.isFinite(desired) && Math.abs(el.currentTime - desired) > 0.35) {
      el.currentTime = Math.max(0, desired);
    }
    if (vp.playing) {
      void el.play().catch(() => undefined);
    } else {
      el.pause();
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps -- avoid reruns on 500ms heartbeats (serverNowMs-only updates)
  }, [
    scene?.previewAssetId,
    scene?.previewAssetType,
    originalUrl,
    vp?.revision,
    vp?.targetAssetId,
    vp?.playing,
    vp?.playbackRate,
  ]);

  return (
    <div className={styles.root}>
      {shownImageUrl && scene?.previewAssetType === 'image' ? (
        <div className={styles.fill}>
          <RotatedImage
            url={shownImageUrl}
            rotationDeg={rot}
            mode="contain"
            viewCamera={sceneView}
            onContentRectChange={setContentRect}
          />
        </div>
      ) : originalUrl && scene?.previewAssetType === 'video' ? (
        <div className={styles.fill}>
          <ContainedVideo
            url={originalUrl}
            rotationDeg={rot}
            videoRef={videoElRef}
            muted
            playsInline
            loop={Boolean(scene?.settings?.loopVideo)}
            preload="auto"
            viewCamera={sceneView}
            onContentRectChange={setContentRect}
            onError={() => {
              // noop: status surfaced in control app; keep presentation clean
            }}
          />
        </div>
      ) : (
        <div className={styles.placeholderBg} />
      )}
      {scene?.previewAssetType === 'image' || scene?.previewAssetType === 'video' ? (
        <SceneGridOverlay grid={scene.grid} viewport={contentRect} />
      ) : null}
      <div className={styles.vignette} />
      {(scene?.previewAssetType === 'image' || scene?.previewAssetType === 'video') && contentRect ? (
        <TokenPathsOverlay
          tokens={scene.tokens ?? []}
          npcTokens={USERS_BRANCH_FEATURES_ENABLED ? visibleNpcTokens : []}
          viewport={contentRect}
          mode="presentation"
          pathSession={tokenPathSession}
        />
      ) : null}
      {(scene?.previewAssetType === 'image' || scene?.previewAssetType === 'video') && contentRect ? (
        <SceneTokensOverlay
          placements={scene.tokens ?? []}
          library={appTokens}
          session={sceneTokensSession}
          viewport={contentRect}
          poseOverrides={pathLivePoses.tokenPoses}
        />
      ) : null}
      {USERS_BRANCH_FEATURES_ENABLED &&
      (scene?.previewAssetType === 'image' || scene?.previewAssetType === 'video') &&
      contentRect ? (
        <SceneNpcTokensOverlay
          placements={visibleNpcTokens}
          library={project?.npcs ?? []}
          session={sceneNpcTokensSession}
          viewport={contentRect}
          grid={scene.grid}
          poseOverrides={pathLivePoses.npcPoses}
        />
      ) : null}
      {USERS_BRANCH_FEATURES_ENABLED &&
      (scene?.previewAssetType === 'image' || scene?.previewAssetType === 'video') &&
      contentRect ? (
        <ScenePlayerTokensOverlay
          library={appPlayers}
          session={scenePlayerTokensSession}
          displayScale={sceneNpcTokensSession.scale ?? DEFAULT_NPC_TOKEN_SESSION_SCALE}
          viewport={contentRect}
          grid={scene.grid}
        />
      ) : null}
      {(scene?.previewAssetType === 'image' || scene?.previewAssetType === 'video') && contentRect ? (
        <SceneTrapsOverlay
          traps={scene.traps ?? []}
          session={sceneTraps}
          viewport={contentRect}
          mode="presentation"
        />
      ) : null}
      {showEffects && (scene?.previewAssetType === 'image' || scene?.previewAssetType === 'video') ? (
        <PixiEffectsOverlay
          state={fxState}
          style={{ zIndex: 6 }}
          viewport={
            contentRect
              ? { x: contentRect.x, y: contentRect.y, w: contentRect.w, h: contentRect.h }
              : undefined
          }
        />
      ) : null}
      {showEffects && (scene?.previewAssetType === 'image' || scene?.previewAssetType === 'video') ? (
        <ExplosionVideoOverlay state={fxState} viewport={contentRect} />
      ) : null}
      {showEffects &&
      (scene?.previewAssetType === 'image' || scene?.previewAssetType === 'video') &&
      scene.darkenScene &&
      contentRect ? (
        <SceneDarknessOverlay state={sdState} overlayAlpha={1} viewport={contentRect} style={{ zIndex: 30 }} />
      ) : null}
      <SceneOverlayHost active={activeMaterialItems.length > 0 || activeNpcItems.length > 0}>
        {activeMaterialItems.map(({ material, layout, legendLayout }) => (
          <React.Fragment key={material.id}>
            <MaterialOverlay
              embedded
              assetId={material.assetId}
              layout={layout}
              materialId={material.id}
              {...(material.legend?.enabled ? { legendMarkers: material.legend.markers ?? [] } : {})}
            />
            {material.legend?.enabled ? (
              <MaterialLegendPanel legend={material.legend} layout={legendLayout} />
            ) : null}
          </React.Fragment>
        ))}
        {activeNpcItems.length > 0 ? <NpcsSceneOverlay embedded items={activeNpcItems} /> : null}
      </SceneOverlayHost>
      {showTitle ? (
        <div className={styles.titleWrap}>
          <div className={compact ? styles.titleCompact : styles.titleFull}>
            {scene?.title ?? 'Выберите сцену в редакторе'}
          </div>
        </div>
      ) : null}
    </div>
  );
}
