import { create } from 'zustand';
import { persist, type PersistStorage, type StorageValue } from 'zustand/middleware';
import { Upload, type UploadOptions } from 'tus-js-client';
import type { Attachment } from '@backspace/shared';
import { useAuthStore } from './authStore';
import { getTokenForOrigin } from '../utils/crossStoreResolvers';
import { getHandle, putHandle, ensurePermission, queryHandlePermission } from '../utils/idbHandles';

export type TransferType = 'upload' | 'download';
export type TransferState =
  | 'queued' | 'active' | 'paused' | 'completed' | 'failed' | 'aborted';

export interface TransferError {
  message: string;
  permanent: boolean;
}

export interface Transfer {
  id: string;
  type: TransferType;
  state: TransferState;
  file: { name: string; size: number; mimetype: string };
  progress: { loaded: number; total: number };
  error?: TransferError;
  channelId?: string;
  tray: boolean;
  origin?: string;

  // Upload-specific
  tusUploadUrl?: string;
  tusExpiresAt?: number;
  fileHandleId?: string;
  attachmentId?: string;
  attachmentFilename?: string;
  uploaderUserId?: string;

  // Download-specific
  sourceUrl?: string;
  destFileHandleId?: string;
  bytesWrittenToDisk?: number;
}

export interface CreateTransferInput {
  type: TransferType;
  file: { name: string; size: number; mimetype: string };
  tray: boolean;
  channelId?: string;
  origin?: string;
  fileHandleId?: string;
  destFileHandleId?: string;
  sourceUrl?: string;
  uploaderUserId?: string;
}

interface TransferStoreState {
  transfers: Map<string, Transfer>;
  /**
   * Mirrors the keys of the module-scoped `liveUploadFiles` map so React components
   * can subscribe to "do we still hold the original File for this transfer?" reactively.
   * Session-scoped — never persisted (the underlying File refs vanish on reload).
   */
  hasInMemoryFile: Set<string>;
}

interface TransferStoreActions {
  createTransfer: (input: CreateTransferInput) => string;
  setState_: (id: string, state: TransferState) => void;
  updateProgress: (id: string, loaded: number) => void;
  setError: (id: string, error: TransferError) => void;
  setTusUrl: (id: string, url: string, expiresAt: number) => void;
  setAttachmentRef: (id: string, attachmentId: string, filename: string) => void;
  remove: (id: string) => void;

  startUpload: (file: Blob, opts: { channelId?: string; tray?: boolean; origin?: string; fileHandleId?: string }) => Promise<string>;
  abortUpload: (id: string) => void;
  pauseUpload: (id: string) => void;
  resumeUpload: (id: string) => Promise<void>;

  startDownload: (url: string, opts: { filename: string; size?: number; mimetype?: string; tray?: boolean }) => Promise<string>;
  abortDownload: (id: string) => void;
  pauseDownload: (id: string) => void;
  resumeDownload: (id: string) => Promise<void>;

  get: (id: string) => Transfer | undefined;
  listVisible: () => Transfer[];
  listForChannel: (channelId: string) => Transfer[];
}

type TransferStore = TransferStoreState & TransferStoreActions;

function uuid(): string {
  return crypto.randomUUID();
}

// Live tus Upload instances — keyed by transferId. Not serializable, never persisted.
const liveUploads = new Map<string, Upload>();

// Original File/Blob references — keyed by transferId. Survive pause; cleared
// on abort or success. Used for in-session resume when no FileSystemFileHandle exists
// (file picker, paste, or drag-drop on Firefox/Safari). Cross-reload resume still
// requires a handle — that path uses idbHandles.
const liveUploadFiles = new Map<string, Blob>();

// Live download AbortControllers — keyed by transferId. Not serializable, never persisted.
const liveDownloads = new Map<string, AbortController>();

// Custom storage that serializes Map<string, Transfer> as an array of entries.
// Only the `transfers` slice is persisted; partialize controls which entries.
const mapAwareStorage: PersistStorage<Pick<TransferStoreState, 'transfers'>> = {
  getItem: (name) => {
    const raw = typeof localStorage !== 'undefined' ? localStorage.getItem(name) : null;
    if (!raw) return null;
    try {
      const parsed = JSON.parse(raw) as { state: { transfers: [string, Transfer][] }; version?: number };
      const stateOut: Pick<TransferStoreState, 'transfers'> = {
        transfers: new Map<string, Transfer>(parsed.state.transfers ?? []),
      };
      return { state: stateOut, version: parsed.version } as StorageValue<Pick<TransferStoreState, 'transfers'>>;
    } catch {
      return null;
    }
  },
  setItem: (name, value) => {
    if (typeof localStorage === 'undefined') return;
    const entries = Array.from(value.state.transfers.entries());
    const payload = JSON.stringify({ state: { transfers: entries }, version: value.version });
    try {
      localStorage.setItem(name, payload);
    } catch (err) {
      console.warn(`[transferStore] persist failed:`, err);
    }
  },
  removeItem: (name) => {
    if (typeof localStorage !== 'undefined') localStorage.removeItem(name);
  },
};

export const useTransferStore = create<TransferStore>()(
  persist(
    (set, get) => {
      // DRY helper: keep the reactive `hasInMemoryFile` set in sync with the
      // module-scoped `liveUploadFiles` map. Both call-sites that mutate the map
      // immediately follow up with this so subscribers re-render.
      const setInMemoryRef = (id: string, present: boolean) => {
        set((s) => {
          const has = s.hasInMemoryFile.has(id);
          if (present === has) return s;
          const next = new Set(s.hasInMemoryFile);
          if (present) next.add(id); else next.delete(id);
          return { hasInMemoryFile: next };
        });
      };

      return ({
      transfers: new Map<string, Transfer>(),
      hasInMemoryFile: new Set<string>(),

      createTransfer: (input) => {
        const id = uuid();
        const t: Transfer = {
          id,
          type: input.type,
          state: 'queued',
          file: input.file,
          progress: { loaded: 0, total: input.file.size },
          tray: input.tray,
          channelId: input.channelId,
          origin: input.origin,
          fileHandleId: input.fileHandleId,
          destFileHandleId: input.destFileHandleId,
          sourceUrl: input.sourceUrl,
          uploaderUserId: input.uploaderUserId,
        };
        set((s) => {
          const next = new Map(s.transfers);
          next.set(id, t);
          return { transfers: next };
        });
        return id;
      },

      setState_: (id, state) => set((s) => {
        const t = s.transfers.get(id);
        if (!t) return s;
        const next = new Map(s.transfers);
        next.set(id, { ...t, state });
        return { transfers: next };
      }),

      updateProgress: (id, loaded) => set((s) => {
        const t = s.transfers.get(id);
        if (!t) return s;
        const next = new Map(s.transfers);
        next.set(id, { ...t, progress: { ...t.progress, loaded } });
        return { transfers: next };
      }),

      setError: (id, error) => set((s) => {
        const t = s.transfers.get(id);
        if (!t) return s;
        const next = new Map(s.transfers);
        next.set(id, { ...t, error, state: 'failed' });
        return { transfers: next };
      }),

      setTusUrl: (id, tusUploadUrl, tusExpiresAt) => set((s) => {
        const t = s.transfers.get(id);
        if (!t) return s;
        const next = new Map(s.transfers);
        next.set(id, { ...t, tusUploadUrl, tusExpiresAt });
        return { transfers: next };
      }),

      setAttachmentRef: (id, attachmentId, filename) => set((s) => {
        const t = s.transfers.get(id);
        if (!t) return s;
        const next = new Map(s.transfers);
        next.set(id, { ...t, attachmentId, attachmentFilename: filename });
        return { transfers: next };
      }),

      remove: (id) => {
        liveUploadFiles.delete(id);
        setInMemoryRef(id, false);
        set((s) => {
          if (!s.transfers.has(id)) return s;
          const next = new Map(s.transfers);
          next.delete(id);
          return { transfers: next };
        });
      },

      startUpload: async (file, opts) => {
        const token = getTokenForOrigin(opts.origin ?? '');
        const user = useAuthStore.getState().user;
        if (!token) throw new Error('Cannot start upload — not authenticated');

        const baseOrigin = opts.origin ?? '';
        const endpoint = `${baseOrigin}/api/files/`;

        const fileLike = file instanceof File
          ? { name: file.name, size: file.size, mimetype: file.type || 'application/octet-stream' }
          : { name: 'upload', size: file.size, mimetype: (file as Blob).type || 'application/octet-stream' };

        const id = get().createTransfer({
          type: 'upload',
          file: fileLike,
          tray: opts.tray ?? true,
          channelId: opts.channelId,
          origin: opts.origin,
          fileHandleId: opts.fileHandleId,
          uploaderUserId: user?.id,
        });

        const tusOpts: UploadOptions = {
          endpoint,
          retryDelays: [0, 1000, 3000, 5000, 10_000],
          metadata: {
            filename: fileLike.name,
            filetype: fileLike.mimetype,
          },
          chunkSize: 5 * 1024 * 1024,
          headers: { Authorization: `Bearer ${token}` },
          onProgress: (loaded: number) => {
            get().updateProgress(id, loaded);
            const t = get().get(id);
            if (t?.state === 'queued') get().setState_(id, 'active');
          },
          onAfterResponse: (_req, res) => {
            // res.getHeader returns string | undefined per tus-js-client v4 types.
            const location = res.getHeader('Location');
            const expires = res.getHeader('Upload-Expires');
            if (location && !get().get(id)?.tusUploadUrl) {
              const expiresMs = expires ? new Date(expires).getTime() : Date.now() + 24 * 60 * 60 * 1000;
              get().setTusUrl(id, location, expiresMs);
            }
          },
          onSuccess: (payload) => {
            try {
              const body = payload.lastResponse?.getBody?.() ?? '';
              const att = JSON.parse(body) as Attachment;
              get().setAttachmentRef(id, att.id, att.filename);
              get().setState_(id, 'completed');
              get().updateProgress(id, fileLike.size);
            } catch (e) {
              const msg = e instanceof Error ? e.message : 'Could not parse upload response';
              get().setError(id, { message: msg, permanent: true });
            } finally {
              liveUploads.delete(id);
              liveUploadFiles.delete(id);
              setInMemoryRef(id, false);
            }
          },
          onError: (err: Error) => {
            const msg = err.message ?? 'Upload error';
            // Treat 4xx as permanent (no retry past tus's own retry chain).
            const permanent = /\b4\d\d\b/.test(msg);
            get().setError(id, { message: msg, permanent });
            liveUploads.delete(id);
          },
        };

        const upload = new Upload(file as File, tusOpts);
        liveUploads.set(id, upload);
        liveUploadFiles.set(id, file);
        setInMemoryRef(id, true);
        upload.start();
        return id;
      },

      abortUpload: (id) => {
        const t = get().get(id);
        const u = liveUploads.get(id);
        if (u) {
          // tus-js-client v4: abort(true) deletes server-side state via DELETE.
          void u.abort(true).catch(() => { /* server may be unreachable; that's OK */ });
          liveUploads.delete(id);
        } else if (t?.tusUploadUrl && !t.attachmentId) {
          // No live instance, but server-side .tus state still exists (e.g., this
          // transfer failed mid-flight or was paused with a stored URL). Send DELETE
          // directly so the partial bytes don't sit on disk until the janitor sweeps.
          const token = getTokenForOrigin(t.origin ?? '');
          if (token) {
            const fullUrl = t.tusUploadUrl.startsWith('http')
              ? t.tusUploadUrl
              : `${t.origin ?? ''}${t.tusUploadUrl}`;
            void fetch(fullUrl, {
              method: 'DELETE',
              headers: {
                Authorization: `Bearer ${token}`,
                'Tus-Resumable': '1.0.0',
              },
            }).catch(() => { /* server unreachable — janitor will eventually clean */ });
          }
        }
        // Keep liveUploadFiles entry — needed for retry-after-abort. Cleared by remove().
        get().setState_(id, 'aborted');
      },

      pauseUpload: (id) => {
        const u = liveUploads.get(id);
        if (u) {
          // abort(false) keeps server-side state — resumable.
          void u.abort(false).catch(() => { /* ignore */ });
          liveUploads.delete(id);
        }
        get().setState_(id, 'paused');
      },

      resumeUpload: async (id) => {
        const t = get().get(id);
        if (!t || t.type !== 'upload') return;

        const token = getTokenForOrigin(t.origin ?? '');
        if (!token) {
          get().setError(id, { message: 'Not authenticated for this instance', permanent: true });
          return;
        }

        // Try in-memory File (same-session resume — file picker, paste, browsers
        // without getAsFileSystemHandle, or retry after abort).
        let blob: Blob | undefined = liveUploadFiles.get(id);

        // Fall back to persisted FileSystemFileHandle (cross-reload resume on Chrome/Edge).
        if (!blob && t.fileHandleId) {
          const handle = await getHandle(t.fileHandleId);
          if (handle) {
            const perm = await ensurePermission(handle, 'read');
            if (perm === 'granted') {
              const handleAny = handle as unknown as { getFile?: () => Promise<File> };
              if (typeof handleAny.getFile === 'function') {
                blob = await handleAny.getFile();
              }
            }
          }
        }

        if (!blob) {
          // No in-memory File (post-reload) AND no FS handle to reacquire bytes from.
          // The user must discard and re-upload. Setting 'failed' lets the orchestrator's
          // mark-failed sweep keep the bubble in failed state and Message.tsx surfaces
          // the discard control. The Retry button is hidden in this case (canRetry gate).
          get().setError(id, {
            message: 'File no longer available — discard and re-upload',
            permanent: true,
          });
          return;
        }

        // Resume the existing tus session if we have a valid, non-expired URL and
        // we weren't aborted. Otherwise start a fresh session reusing this transferId
        // (retry-after-abort, retry-after-failure, retry-after-expiry).
        const canResume =
          !!t.tusUploadUrl &&
          t.state !== 'aborted' &&
          Date.now() <= (t.tusExpiresAt ?? 0);

        if (!canResume) {
          // Reset transient state so a fresh tus session starts cleanly.
          set((s) => {
            const cur = s.transfers.get(id);
            if (!cur) return s;
            const next = new Map(s.transfers);
            next.set(id, {
              ...cur,
              tusUploadUrl: undefined,
              tusExpiresAt: undefined,
              attachmentId: undefined,
              attachmentFilename: undefined,
              error: undefined,
              progress: { loaded: 0, total: blob!.size },
            });
            return { transfers: next };
          });
        }

        get().setState_(id, 'queued');

        const fileLike = blob instanceof File
          ? { name: blob.name, mimetype: blob.type || 'application/octet-stream' }
          : { name: t.file.name, mimetype: blob.type || t.file.mimetype };

        const tusOpts: UploadOptions = {
          endpoint: t.origin ? `${t.origin}/api/files/` : '/api/files/',
          retryDelays: [0, 1000, 3000, 5000, 10_000],
          chunkSize: 5 * 1024 * 1024,
          headers: { Authorization: `Bearer ${token}` },
          ...(canResume
            ? { uploadUrl: t.tusUploadUrl }
            : { metadata: { filename: fileLike.name, filetype: fileLike.mimetype } }),
          onProgress: (loaded: number) => {
            get().updateProgress(id, loaded);
            const cur = get().get(id);
            if (cur?.state !== 'active') get().setState_(id, 'active');
          },
          onAfterResponse: (_req, res) => {
            // Capture the new Location for fresh sessions; resume reuses the existing one.
            if (canResume) return;
            const location = res.getHeader('Location');
            const expires = res.getHeader('Upload-Expires');
            if (location && !get().get(id)?.tusUploadUrl) {
              const expiresMs = expires ? new Date(expires).getTime() : Date.now() + 24 * 60 * 60 * 1000;
              get().setTusUrl(id, location, expiresMs);
            }
          },
          onSuccess: (payload) => {
            try {
              const body = payload.lastResponse?.getBody?.() ?? '';
              const att = JSON.parse(body) as Attachment;
              get().setAttachmentRef(id, att.id, att.filename);
              get().setState_(id, 'completed');
              get().updateProgress(id, blob!.size);
            } catch (e) {
              const msg = e instanceof Error ? e.message : 'Upload completed but parse failed';
              get().setError(id, { message: msg, permanent: true });
            } finally {
              liveUploads.delete(id);
              liveUploadFiles.delete(id);
              setInMemoryRef(id, false);
            }
          },
          onError: (err: Error) => {
            const msg = err.message ?? 'Upload error';
            const permanent = /\b4\d\d\b/.test(msg);
            get().setError(id, { message: msg, permanent });
            liveUploads.delete(id);
          },
        };

        // Retain the resolved blob in the in-memory map so a subsequent retry-after-failure
        // (e.g., transient network error) can resume without going through the handle path.
        liveUploadFiles.set(id, blob);
        setInMemoryRef(id, true);

        const upload = new Upload(blob as File, tusOpts);
        liveUploads.set(id, upload);
        upload.start();
      },

      startDownload: async (url, opts) => {
        const fileLike = {
          name: opts.filename,
          size: opts.size ?? 0,
          mimetype: opts.mimetype ?? 'application/octet-stream',
        };
        const id = get().createTransfer({
          type: 'download',
          file: fileLike,
          tray: opts.tray ?? true,
          sourceUrl: url,
        });

        const controller = new AbortController();
        liveDownloads.set(id, controller);
        get().setState_(id, 'active');

        // FS Access path: only when the API exists. We DON'T require opts.size —
        // size is unknown for some downloads and we want resume capability anyway.
        const supportsFs = typeof (window as unknown as { showSaveFilePicker?: unknown }).showSaveFilePicker === 'function';

        // Hoisted so the catch block can flush partial bytes on AbortError.
        // Without close(), the FS Access writable's buffer is discarded and the
        // file is left at its truncated (open-time) size — which is 0 since we
        // open with keepExistingData: false. Resuming would then re-download
        // from byte 0 instead of continuing from the offset we'd reached.
        let writable: FileSystemWritableFileStream | undefined;
        try {
          if (supportsFs) {
            const showSavePicker = (window as unknown as {
              showSaveFilePicker: (opts: { suggestedName: string }) => Promise<FileSystemFileHandle>;
            }).showSaveFilePicker;
            let handle: FileSystemFileHandle;
            try {
              handle = await showSavePicker({ suggestedName: opts.filename });
            } catch {
              // User canceled the picker — abort cleanly.
              get().setState_(id, 'aborted');
              liveDownloads.delete(id);
              return id;
            }
            const handleId = `dl-${id}`;
            await putHandle(handleId, handle);
            // Persist the handle key on the transfer.
            set((s) => {
              const t = s.transfers.get(id);
              if (!t) return s;
              const next = new Map(s.transfers);
              next.set(id, { ...t, destFileHandleId: handleId, bytesWrittenToDisk: 0 });
              return { transfers: next };
            });
            writable = await handle.createWritable({ keepExistingData: false });
            const resp = await fetch(url, { signal: controller.signal });
            if (!resp.ok || !resp.body) throw new Error(`Download failed: ${resp.status}`);
            const reader = resp.body.getReader();
            let written = 0;
            for (;;) {
              const { value, done } = await reader.read();
              if (done) break;
              await writable.write(value);
              written += value.byteLength;
              get().updateProgress(id, written);
              set((s) => {
                const t = s.transfers.get(id);
                if (!t) return s;
                const next = new Map(s.transfers);
                next.set(id, { ...t, bytesWrittenToDisk: written });
                return { transfers: next };
              });
            }
            await writable.close();
            writable = undefined;
            get().setState_(id, 'completed');
          } else {
            // Blob fallback — accumulate in memory, then trigger anchor click.
            const resp = await fetch(url, { signal: controller.signal });
            if (!resp.ok) throw new Error(`Download failed: ${resp.status}`);
            const total = Number(resp.headers.get('Content-Length') ?? opts.size ?? 0);
            const chunks: Uint8Array[] = [];
            const reader = resp.body!.getReader();
            let loaded = 0;
            for (;;) {
              const { value, done } = await reader.read();
              if (done) break;
              chunks.push(value);
              loaded += value.byteLength;
              if (total) get().updateProgress(id, loaded);
            }
            const blob = new Blob(chunks as BlobPart[], { type: fileLike.mimetype });
            const objUrl = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = objUrl;
            a.download = opts.filename;
            a.click();
            URL.revokeObjectURL(objUrl);
            get().setState_(id, 'completed');
          }
        } catch (err) {
          // Always flush whatever we managed to write before bubbling out — on
          // AbortError so resume can pick up at the right offset, and on real
          // errors so disk state matches reality (no half-buffered ghost bytes).
          if (writable) {
            try { await writable.close(); } catch { /* best effort */ }
            writable = undefined;
          }
          if (err instanceof Error && err.name === 'AbortError') {
            // Aborted via abortDownload/pauseDownload — state already set there.
          } else {
            const msg = err instanceof Error ? err.message : 'Download failed';
            const permanent = /\b4\d\d\b/.test(msg);
            get().setError(id, { message: msg, permanent });
          }
        } finally {
          liveDownloads.delete(id);
        }

        return id;
      },

      abortDownload: (id) => {
        const c = liveDownloads.get(id);
        if (c) c.abort();
        liveDownloads.delete(id);
        get().setState_(id, 'aborted');
      },

      pauseDownload: (id) => {
        const c = liveDownloads.get(id);
        if (c) c.abort();
        liveDownloads.delete(id);
        get().setState_(id, 'paused');
      },

      resumeDownload: async (id) => {
        const t = get().get(id);
        if (!t || t.type !== 'download' || !t.sourceUrl) return;
        if (!t.destFileHandleId) {
          get().setError(id, { message: 'No destination handle — cannot resume', permanent: true });
          return;
        }
        const handle = await getHandle(t.destFileHandleId);
        if (!handle) {
          get().setError(id, { message: 'Destination handle missing', permanent: true });
          return;
        }
        const perm = await ensurePermission(handle, 'readwrite');
        if (perm !== 'granted') {
          get().setError(id, { message: 'Permission denied', permanent: false });
          return;
        }
        const handleAny = handle as unknown as {
          getFile?: () => Promise<File>;
          createWritable?: (opts: { keepExistingData: boolean }) => Promise<FileSystemWritableFileStream>;
        };
        const fileNow = await handleAny.getFile!();
        const offset = fileNow.size;
        const writable = await handleAny.createWritable!({ keepExistingData: true });
        await (writable as unknown as { seek: (pos: number) => Promise<void> }).seek(offset);

        const controller = new AbortController();
        liveDownloads.set(id, controller);
        get().setState_(id, 'active');
        try {
          const resp = await fetch(t.sourceUrl, {
            signal: controller.signal,
            headers: { Range: `bytes=${offset}-` },
          });
          if (!resp.ok && resp.status !== 206) throw new Error(`Resume failed: ${resp.status}`);
          const reader = resp.body!.getReader();
          let written = offset;
          for (;;) {
            const { value, done } = await reader.read();
            if (done) break;
            await writable.write(value);
            written += value.byteLength;
            get().updateProgress(id, written);
            set((s) => {
              const cur = s.transfers.get(id);
              if (!cur) return s;
              const next = new Map(s.transfers);
              next.set(id, { ...cur, bytesWrittenToDisk: written });
              return { transfers: next };
            });
          }
          await writable.close();
          get().setState_(id, 'completed');
        } catch (err) {
          // Flush partial bytes before bubbling out so the next resume's offset
          // (read via getFile().size) reflects what's actually on disk.
          try { await writable.close(); } catch { /* best effort */ }
          if (err instanceof Error && err.name === 'AbortError') {
            get().setState_(id, 'paused');
          } else {
            const msg = err instanceof Error ? err.message : 'Resume failed';
            const permanent = /\b4\d\d\b/.test(msg);
            get().setError(id, { message: msg, permanent });
          }
        } finally {
          liveDownloads.delete(id);
        }
      },

      get: (id) => get().transfers.get(id),
      listVisible: () => Array.from(get().transfers.values()).filter((t) => t.tray),
      listForChannel: (channelId) =>
        Array.from(get().transfers.values()).filter((t) => t.channelId === channelId),
    });
    },
    {
      name: 'transferStore@v1',
      storage: mapAwareStorage,
      version: 1,
      // Only persist transfers that are meaningful to rehydrate:
      //   - completed uploads waiting for the deferred POST (have attachmentId)
      //   - in-flight uploads with a tusUploadUrl (resumable on reload)
      //   - failed transfers (user may want to retry / discard)
      // Drop active in-flight without a tusUrl (their state is lost), drop aborted (user discarded).
      partialize: (s) => ({
        transfers: new Map(
          Array.from(s.transfers.entries()).filter(([, t]) =>
            t.attachmentId !== undefined ||
            t.tusUploadUrl !== undefined ||
            t.state === 'failed' ||
            t.state === 'paused'
          )
        ),
      }),
      onRehydrateStorage: () => (state, error) => {
        if (error || !state) return;
        // Normalize on the next tick so the store is fully wired before we mutate
        // it, and so async work (handle probe + auto-resume) doesn't block the
        // rehydrate path.
        queueMicrotask(() => {
          void normalizeRehydratedTransfers();
        });
      },
    },
  ),
);

/**
 * Boot-time normalization of rehydrated transfers.
 *
 * 1. Demotes any leaked 'active' state to 'paused' (defensive — partialize already
 *    filters most, but a transfer mid-progress can still slip through if it had a
 *    tusUploadUrl).
 * 2. Marks bytes-unrecoverable paused transfers as 'failed' with an actionable
 *    message — we have no in-memory File post-reload and no FS handle to reacquire
 *    bytes from, so showing a paused state would be misleading.
 * 3. For paused transfers with a stored handle whose permission is already
 *    'granted', silently auto-resumes. For 'prompt' / 'denied', leaves paused so
 *    the user's explicit Resume click provides the user-gesture for
 *    `requestPermission`.
 *
 * Idempotent — re-running is harmless.
 */
async function normalizeRehydratedTransfers(): Promise<void> {
  const store = useTransferStore.getState();
  const transfers = Array.from(store.transfers.values());

  for (const t of transfers) {
    // 1) Demote any leaked 'active' state — no live worker exists post-reload.
    if (t.state === 'active') {
      store.setState_(t.id, 'paused');
    }

    const current = useTransferStore.getState().get(t.id);
    if (!current) continue;

    // 2) Bytes-unrecoverable paused transfers → immediately fail.
    if (current.state === 'paused') {
      const isUpload = current.type === 'upload';
      const handleId = isUpload ? current.fileHandleId : current.destFileHandleId;
      if (!handleId) {
        store.setError(current.id, {
          message: isUpload
            ? 'File no longer available — discard and re-upload'
            : 'Download cannot resume — bytes lost. Restart the download.',
          permanent: true,
        });
        continue;
      }

      // 3) Auto-resume when permission is already 'granted'.
      try {
        const handle = await getHandle(handleId);
        if (!handle) {
          store.setError(current.id, {
            message: isUpload
              ? 'File handle missing — discard and re-upload'
              : 'Destination handle missing — restart the download',
            permanent: true,
          });
          continue;
        }
        const mode: 'read' | 'readwrite' = isUpload ? 'read' : 'readwrite';
        const perm = await queryHandlePermission(handle, mode);
        if (perm === 'granted') {
          // Use the live store reference so subsequent state changes are visible
          // to subscribers.
          if (isUpload) {
            void useTransferStore.getState().resumeUpload(current.id);
          } else {
            void useTransferStore.getState().resumeDownload(current.id);
          }
        }
        // perm === 'prompt' or 'denied' → user must click Resume to trigger
        // requestPermission with a user-gesture.
      } catch {
        // Probe failed (IDB unavailable, etc.) — leave paused. User can click
        // Resume to retry through the normal path.
      }
    }
  }
}
