import { useCallback, useEffect, useRef, useState } from "react";
import type { Material } from "@/shared/types/material";

export interface TimelineSegment {
  id: string;
  materialId: string;
  materialName: string;
  start: number;
  end: number;
  sourceStart: number;
  sourceEnd: number;
  color: string;
}

export interface CustomAssignment {
  material_path: string;
  start: number;
  end: number;
  source_start: number;
  source_end?: number;
}

const COLORS = ["#8b5cf6", "#ec4899", "#06b6d4", "#f59e0b", "#10b981", "#f97316"];

/** Serializable subset for localStorage */
interface SegmentSnapshot {
  materialId: string;
  start: number;
  end: number;
  sourceStart: number;
  sourceEnd: number;
}

/** Get effective duration of a segment (clipped range or full material duration) */
function getEffectiveDuration(
  seg: { sourceStart: number; sourceEnd: number; materialId: string },
  mats: Material[]
): number {
  const mat = mats.find((m) => m.id === seg.materialId);
  const matDur = mat?.duration_sec ?? 0;
  if (seg.sourceEnd > seg.sourceStart) return seg.sourceEnd - seg.sourceStart;
  if (seg.sourceStart > 0) return Math.max(matDur - seg.sourceStart, 0);
  return matDur;
}

/**
 * Recalculate segment start/end positions based on effective durations.
 * - Segments placed sequentially by effective duration
 * - Segments exceeding audioDuration keep their positions (overflow, start >= duration)
 * - Last visible segment is capped/extended to exactly audioDuration (loop fill)
 */
function recalcPositions(
  segs: TimelineSegment[],
  mats: Material[],
  duration: number
): TimelineSegment[] {
  if (segs.length === 0 || duration <= 0) return segs;

  const fallbackDur = duration / segs.length;
  let cursor = 0;
  const result = segs.map((seg) => {
    const effDur = getEffectiveDuration(seg, mats);
    const dur = effDur > 0 ? effDur : fallbackDur;
    const newSeg = { ...seg, start: cursor, end: cursor + dur };
    cursor += dur;
    return newSeg;
  });

  // Find last segment that starts before audioDuration
  let lastVisibleIdx = -1;
  for (let i = result.length - 1; i >= 0; i--) {
    if (result[i].start < duration) {
      lastVisibleIdx = i;
      break;
    }
  }

  // Cap/extend last visible segment to exactly audioDuration
  if (lastVisibleIdx >= 0) {
    result[lastVisibleIdx] = { ...result[lastVisibleIdx], end: duration };
  }

  return result;
}

interface UseTimelineEditorOptions {
  audioDuration: number;
  materials: Material[];
  selectedMaterials: string[];
  storageKey?: string;
}

export const useTimelineEditor = ({
  audioDuration,
  materials,
  selectedMaterials,
  storageKey,
}: UseTimelineEditorOptions) => {
  const [segments, setSegments] = useState<TimelineSegment[]>([]);
  const prevKey = useRef("");
  const restoredRef = useRef(false);

  // Refs for stable callbacks (avoid recreating on every materials/duration change)
  const materialsRef = useRef(materials);
  const audioDurationRef = useRef(audioDuration);

  useEffect(() => {
    materialsRef.current = materials;
  }, [materials]);

  useEffect(() => {
    audioDurationRef.current = audioDuration;
  }, [audioDuration]);

  // Build a durationsKey so segments re-init when material durations become available
  const durationsKey = selectedMaterials
    .map((id) => materials.find((m) => m.id === id)?.duration_sec ?? 0)
    .join(",");

  // Build a cache key from materials + duration
  const cacheKey = `${selectedMaterials.join(",")}_${audioDuration.toFixed(1)}`;
  const lsKey = storageKey ? `vigent_${storageKey}_timeline` : null;

  const initSegments = useCallback(() => {
    if (selectedMaterials.length === 0 || audioDuration <= 0) {
      setSegments([]);
      return;
    }

    // Try restore from localStorage
    if (lsKey) {
      try {
        const raw = localStorage.getItem(lsKey);
        if (raw) {
          const saved = JSON.parse(raw) as { key: string; segments: SegmentSnapshot[] };
          if (saved.key === cacheKey && saved.segments.length === selectedMaterials.length) {
            const allMatch = saved.segments.every(
              (s, i) => s.materialId === selectedMaterials[i] || saved.segments.some((ss) => ss.materialId === selectedMaterials[i])
            );
            if (allMatch) {
              const restored: TimelineSegment[] = saved.segments.map((s, i) => {
                const mat = materials.find((m) => m.id === s.materialId);
                return {
                  id: `seg-${i}-${Date.now()}`,
                  materialId: s.materialId,
                  materialName: mat?.scene || mat?.name || s.materialId,
                  start: 0,
                  end: 0,
                  sourceStart: s.sourceStart,
                  sourceEnd: s.sourceEnd,
                  color: COLORS[i % COLORS.length],
                };
              });
              setSegments(recalcPositions(restored, materials, audioDuration));
              restoredRef.current = true;
              return;
            }
          }
        }
      } catch {
        // ignore parse errors
      }
    }

    // Create fresh segments — positions derived by recalcPositions
    const newSegments: TimelineSegment[] = selectedMaterials.map((matId, i) => {
      const mat = materials.find((m) => m.id === matId);
      return {
        id: `seg-${i}-${Date.now()}`,
        materialId: matId,
        materialName: mat?.scene || mat?.name || matId,
        start: 0,
        end: 0,
        sourceStart: 0,
        sourceEnd: 0,
        color: COLORS[i % COLORS.length],
      };
    });

    setSegments(recalcPositions(newSegments, materials, audioDuration));
  }, [audioDuration, materials, selectedMaterials, lsKey, cacheKey]);

  // Auto-init when selectedMaterials, audioDuration, or material durations change
  useEffect(() => {
    const key = `${selectedMaterials.join(",")}_${audioDuration}_${durationsKey}`;
    if (key !== prevKey.current) {
      prevKey.current = key;
      initSegments();
    }
  }, [selectedMaterials, audioDuration, durationsKey, initSegments]);

  // Persist segments to localStorage on change (debounced)
  useEffect(() => {
    if (!lsKey || segments.length === 0) return;
    const timeout = setTimeout(() => {
      const snapshots: SegmentSnapshot[] = segments.map((s) => ({
        materialId: s.materialId,
        start: s.start,
        end: s.end,
        sourceStart: s.sourceStart,
        sourceEnd: s.sourceEnd,
      }));
      localStorage.setItem(lsKey, JSON.stringify({ key: cacheKey, segments: snapshots }));
    }, 300);
    return () => clearTimeout(timeout);
  }, [segments, lsKey, cacheKey]);

  const reorderSegments = useCallback(
    (fromIdx: number, toIdx: number) => {
      setSegments((prev) => {
        if (fromIdx < 0 || toIdx < 0 || fromIdx >= prev.length || toIdx >= prev.length) return prev;
        if (fromIdx === toIdx) return prev;
        const next = [...prev];
        // Move the segment: remove from old position, insert at new position
        const [moved] = next.splice(fromIdx, 1);
        next.splice(toIdx, 0, moved);
        return recalcPositions(next, materialsRef.current, audioDurationRef.current);
      });
    },
    []
  );

  const setSourceRange = useCallback(
    (id: string, sourceStart: number, sourceEnd: number) => {
      setSegments((prev) => {
        const updated = prev.map((s) => (s.id === id ? { ...s, sourceStart, sourceEnd } : s));
        return recalcPositions(updated, materialsRef.current, audioDurationRef.current);
      });
    },
    []
  );

  const toCustomAssignments = useCallback((): CustomAssignment[] => {
    const duration = audioDurationRef.current;
    return segments
      .filter((seg) => seg.start < duration)
      .map((seg) => {
        const mat = materialsRef.current.find((m) => m.id === seg.materialId);
        return {
          material_path: mat?.path || seg.materialId,
          start: seg.start,
          end: seg.end,
          source_start: seg.sourceStart,
          source_end: seg.sourceEnd > seg.sourceStart ? seg.sourceEnd : undefined,
        };
      });
  }, [segments]);

  return {
    segments,
    initSegments,
    reorderSegments,
    setSourceRange,
    toCustomAssignments,
  };
};
