'use client';

// Auto-save + auto-restore hook for the editor.
// In "auto" / "server" mode: writes to backend API when logged in.
// Falls back to IndexedDB if offline.

import { useEffect, useRef, useState } from 'react';
import { useEditor } from '@/store/editor';
import { useAuth } from '@/store/auth';
import type {
  Clip,
  ExportSettings,
  MediaAsset,
  SilenceSegment,
  SubtitleCue,
  SubtitleStyle,
  Track,
} from '@/types';
import * as mirror from './server-mirror';
import type { ProjectSummary } from './persistence';

const SAVE_DEBOUNCE_MS = 3000;

export interface UsePersistenceResult {
  lastSavedAt: number | null;
  saving: boolean;
  restoreAvailable: boolean;
  restorePayload: { id: string; name: string; savedAt: number } | null;
  restoreProject: () => Promise<void>;
  dismissRestore: () => void;
  clearAll: () => Promise<void>;
  projects: ProjectSummary[];
  refreshProjects: () => Promise<void>;
}

export function usePersistence(): UsePersistenceResult {
  const [lastSavedAt, setLastSavedAt] = useState<number | null>(null);
  const [saving, setSaving] = useState(false);
  const [restoreAvailable, setRestoreAvailable] = useState(false);
  const [restorePayload, setRestorePayload] = useState<
    { id: string; name: string; savedAt: number } | null
  >(null);
  const [projects, setProjects] = useState<ProjectSummary[]>([]);
  const saveTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
  const hydratedRef = useRef(false);

  // Computed auth context
  const authUser = useAuth((s) => s.user);
  const authInitialized = useAuth((s) => s.initialized);
  const storageMode = useAuth((s) => s.storageMode);
  const ctx = { mode: storageMode, isLoggedIn: !!authUser };

  // On mount: check if a current project exists with content.
  useEffect(() => {
    let cancelled = false;
    (async () => {
      try {
        const currentId = await mirror.getCurrentProjectId();
        if (!currentId) {
          const s = useEditor.getState();
          await mirror.saveProject(
            s.projectId,
            s.projectName,
            s.projectCreatedAt,
            {
              tracks: s.tracks,
              clips: s.clips,
              subtitles: s.subtitles,
              silenceSegments: s.silenceSegments,
              selection: s.selection,
              assetsMeta: [],
              exportSettings: s.exportSettings,
              subtitleStyle: s.subtitleStyle,
            },
            ctx,
          );
          await mirror.setCurrentProjectId(s.projectId);
          await refreshProjects();
          hydratedRef.current = true;
          return;
        }
        const stored = await mirror.loadProject(currentId, ctx);
        if (cancelled) return;
        if (stored && hasContent(stored)) {
          setRestorePayload({
            id: stored.id,
            name: stored.name,
            savedAt: stored.updatedAt,
          });
          setRestoreAvailable(true);
        } else {
          hydratedRef.current = true;
        }
        await refreshProjects();
      } catch (err) {
        console.warn('[persistence] init failed', err);
      }
    })();
    return () => {
      cancelled = true;
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  // Subscribe to store changes for auto-save.
  useEffect(() => {
    const unsub = useEditor.subscribe((state, prev) => {
      if (!hydratedRef.current) return;
      if (!shouldAutoSave(state, prev)) return;
      if (saveTimer.current) clearTimeout(saveTimer.current);
      saveTimer.current = setTimeout(() => {
        void doSave(state);
      }, SAVE_DEBOUNCE_MS);
    });
    return () => {
      unsub();
      if (saveTimer.current) clearTimeout(saveTimer.current);
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [ctx.mode, ctx.isLoggedIn]);

  // When user just logged in, push the current local project to server
  // (so their work-in-progress becomes available on other devices).
  const prevUserIdRef = useRef<string | null>(null);
  useEffect(() => {
    if (!authInitialized) return;
    const currentUserId = authUser?.id ?? null;
    const prevUserId = prevUserIdRef.current;
    prevUserIdRef.current = currentUserId;

    // Skip if no change or first init
    if (currentUserId === prevUserId) return;
    // Skip if not logged in
    if (!currentUserId) return;
    // If we just transitioned from null → logged-in, sync current project to server.
    if (prevUserId === null) {
      const state = useEditor.getState();
      void doSave(state);
      void refreshProjects();
    } else {
      // Switched to a different user — refresh projects list
      void refreshProjects();
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [authUser?.id, authInitialized]);

  async function doSave(state: ReturnType<typeof useEditor.getState>): Promise<void> {
    setSaving(true);
    try {
      const previousIds = new Set<string>();
      for (const a of state.assets) {
        previousIds.add(a.id);
        try {
          if (a.url) {
            const resp = await fetch(a.url);
            const blob = await resp.blob();
            const audioBuffer = a.audioBuffer
              ? await audioBufferToArrayBuffer(a.audioBuffer)
              : undefined;
            await mirror.saveAssetBlob(state.projectId, a.id, blob, audioBuffer, ctx);
          }
        } catch {
          /* ignore */
        }
      }
      const knownIds = new Set(await mirror.listAssetIdsForProject(state.projectId, ctx));
      for (const id of knownIds) {
        if (!previousIds.has(id)) await mirror.deleteAssetBlob(state.projectId, id, ctx);
      }

      const assetsMeta = state.assets.map(stripAssetBlob);

      const savedAt = await mirror.saveProject(
        state.projectId,
        state.projectName,
        state.projectCreatedAt,
        {
          tracks: state.tracks,
          clips: state.clips,
          subtitles: state.subtitles,
          silenceSegments: state.silenceSegments,
          selection: state.selection,
          assetsMeta,
          exportSettings: state.exportSettings,
          subtitleStyle: state.subtitleStyle,
        },
        ctx,
      );
      setLastSavedAt(savedAt);
      await refreshProjects();
    } catch (err) {
      console.warn('[persistence] save failed', err);
    } finally {
      setSaving(false);
    }
  }

  async function refreshProjects(): Promise<void> {
    try {
      const list = await mirror.listProjects(ctx);
      setProjects(list);
    } catch {
      /* ignore */
    }
  }

  async function restoreProject(): Promise<void> {
    if (!restorePayload) return;
    try {
      const stored = await mirror.loadProject(restorePayload.id, ctx);
      if (!stored) return;

      const restoredAssets: MediaAsset[] = [];
      const metaList = (stored.assetsMeta as Omit<MediaAsset, 'url' | 'audioBuffer'>[]) ?? [];
      for (const meta of metaList) {
        const blob = await mirror.loadAssetBlob(stored.id, meta.id, ctx);
        if (!blob) continue;
        const file = new File([blob.file], meta.name, {
          type: blob.file.type || (meta.kind === 'audio' ? 'audio/wav' : 'video/mp4'),
        });
        const url = URL.createObjectURL(file);
        let audioBuffer: AudioBuffer | undefined;
        if (blob.audioBuffer) {
          audioBuffer = await arrayBufferToAudioBuffer(blob.audioBuffer);
        }
        restoredAssets.push({ ...meta, url, size: blob.file.size, audioBuffer });
      }

      useEditor.setState({
        projectId: stored.id,
        projectName: stored.name,
        projectCreatedAt: stored.createdAt,
        tracks: (stored.tracks as Track[]) ?? useEditor.getState().tracks,
        clips: (stored.clips as Clip[]) ?? [],
        subtitles: (stored.subtitles as SubtitleCue[]) ?? [],
        silenceSegments:
          (stored.silenceSegments as SilenceSegment[]) ?? [],
        selection: (stored.selection as { clipId?: string; trackId?: string }) ?? {},
        assets: restoredAssets,
        currentTime: 0,
        playing: false,
        history: [],
        historyIndex: -1,
        ...(stored.subtitleStyle
          ? { subtitleStyle: stored.subtitleStyle as SubtitleStyle }
          : {}),
        ...(stored.exportSettings
          ? { exportSettings: stored.exportSettings as ExportSettings }
          : {}),
      });
      hydratedRef.current = true;
      setRestoreAvailable(false);
      setRestorePayload(null);
      setLastSavedAt(restorePayload.savedAt);
      useEditor.getState().setToast({ message: 'กู้คืนโปรเจกต์ล่าสุดเรียบร้อย', level: 'success' });
      await refreshProjects();
    } catch (err) {
      console.warn('[persistence] restore failed', err);
      useEditor.getState().setToast({ message: 'กู้คืนล้มเหลว — เริ่มโปรเจกต์ใหม่', level: 'error' });
    }
  }

  function dismissRestore(): void {
    setRestoreAvailable(false);
    hydratedRef.current = true;
  }

  async function clearAll(): Promise<void> {
    const s = useEditor.getState();
    await mirror.deleteAllBlobsForProject(s.projectId, ctx);
    useEditor.setState({
      assets: [],
      clips: [],
      subtitles: [],
      silenceSegments: [],
      selection: {},
      currentTime: 0,
      playing: false,
      history: [],
      historyIndex: -1,
    });
    setLastSavedAt(null);
    useEditor.getState().setToast({ message: 'ล้างข้อมูลเรียบร้อย', level: 'success' });
    await refreshProjects();
  }

  return {
    lastSavedAt,
    saving,
    restoreAvailable,
    restorePayload,
    restoreProject,
    dismissRestore,
    clearAll,
    projects,
    refreshProjects,
  };
}

// ----- Helpers -----

interface MediaAssetMeta extends Omit<MediaAsset, 'url' | 'audioBuffer'> {}

function stripAssetBlob(asset: MediaAsset): MediaAssetMeta {
  const { url: _url, audioBuffer: _ab, ...rest } = asset;
  return rest;
}

interface ProjectWithClips {
  clips?: unknown;
  subtitles?: unknown;
  assetsMeta?: unknown;
}

function hasContent(project: ProjectWithClips): boolean {
  const clips = (project.clips as unknown[] | undefined) ?? [];
  const subtitles = (project.subtitles as unknown[] | undefined) ?? [];
  const assets = (project.assetsMeta as unknown[] | undefined) ?? [];
  return clips.length > 0 || subtitles.length > 0 || assets.length > 0;
}

function shouldAutoSave(state: ReturnType<typeof useEditor.getState>, prev: ReturnType<typeof useEditor.getState>): boolean {
  if (state.toast !== prev.toast) return false;
  if (state.playing !== prev.playing) return false;
  if (state.currentTime !== prev.currentTime) return false;
  if (state.selection !== prev.selection) return false;
  if (state.history !== prev.history) return true;
  return (
    state.tracks !== prev.tracks ||
    state.clips !== prev.clips ||
    state.subtitles !== prev.subtitles ||
    state.silenceSegments !== prev.silenceSegments ||
    state.assets !== prev.assets ||
    state.exportSettings !== prev.exportSettings ||
    state.subtitleStyle !== prev.subtitleStyle ||
    state.projectName !== prev.projectName
  );
}

async function audioBufferToArrayBuffer(buffer: AudioBuffer): Promise<ArrayBuffer> {
  const totalLen = buffer.length * buffer.numberOfChannels * 4;
  const out = new ArrayBuffer(totalLen);
  const view = new Float32Array(out);
  let offset = 0;
  for (let ch = 0; ch < buffer.numberOfChannels; ch++) {
    const data = buffer.getChannelData(ch);
    view.set(data, offset);
    offset += data.length;
  }
  return out;
}

async function arrayBufferToAudioBuffer(buffer: ArrayBuffer): Promise<AudioBuffer> {
  const sampleRate = 44100;
  const view = new Float32Array(buffer);
  const ctx = new OfflineAudioContext(1, view.length, sampleRate);
  const audioBuffer = ctx.createBuffer(1, view.length, sampleRate);
  audioBuffer.copyToChannel(view, 0);
  return audioBuffer;
}
