// Download-center domain: the yt-dlp job lifecycle, per-user profiles, share links/recipients, the
// phase-2 editor, storyboards, and the admin storage/quota views. Self-contained — its types only
// reference each other, and its methods only touch the shared `req`/`getActiveAccount` from core.
import { req, getActiveAccount } from "./core";

export interface DownloadSpec {
  mode: "av" | "v" | "a"; // audio+video | video-only | audio-only
  max_height: number | null;
  container: string | null;
  audio_format: string | null;
  vcodec: string | null;
  embed_subs: boolean;
  embed_chapters: boolean;
  embed_thumbnail: boolean;
  sponsorblock: boolean;
}

export interface DownloadProfile {
  id: number;
  name: string;
  spec: DownloadSpec;
  is_builtin: boolean;
  sort_order: number;
}

interface DownloadAsset {
  id: number;
  status: string;
  title: string | null;
  uploader: string | null;
  uploader_url: string | null; // yt-dlp channel/uploader page URL (auto; null for e.g. reddit)
  upload_date: string | null;
  duration_s: number | null;
  size_bytes: number | null;
  width: number | null;
  height: number | null;
  container: string | null;
  thumbnail_url: string | null;
  expires_at: string | null;
}

export type DownloadStatus = "queued" | "running" | "paused" | "done" | "error" | "canceled";
// "download" (default) or "edit" (a per-user trim/crop derivative cut from another job).
// Local (not exported): consumers compare `job.job_kind` against literals, never name the type.
type DownloadJobKind = "download" | "edit";

// Phase-2 editor recipe. A single `trim` (one output file) or a `segments` cut-list joined into
// one file. `accurate` re-encodes for a frame-accurate cut (a crop always re-encodes).
export interface EditSpec {
  trim?: { start_s: number; end_s?: number };
  segments?: { start_s: number; end_s: number }[];
  crop?: { x: number; y: number; w: number; h: number };
  accurate?: boolean;
}

export interface StoryboardMeta {
  available: boolean;
  cols?: number;
  rows?: number;
  count?: number;
  interval_s?: number;
  url?: string;
}

export interface DownloadJob {
  id: number;
  status: DownloadStatus;
  progress: number;
  speed_bps: number | null;
  eta_s: number | null;
  phase: string | null;
  error: string | null; // raw yt-dlp/ffmpeg text (technical detail)
  error_code: string | null; // failure category, see backend downloads.errors.classify
  queue_pos: number;
  created_at: string | null;
  source_kind: string;
  source_ref: string;
  source_url: string | null; // clean "downloaded from" URL (null for edit clips)
  poster_url: string | null; // locally-generated poster, fallback when the source had no thumbnail
  profile_id: number | null;
  spec: DownloadSpec;
  display_name: string | null;
  display_uploader: string | null; // user override of the channel name
  display_uploader_url: string | null; // user override of the channel link
  extra_links: string[]; // extra reference URLs (beyond source_url)
  job_kind?: DownloadJobKind; // "download" (default) | "edit" (a per-user trim/crop derivative)
  source_job_id?: number | null; // parent download an edit was cut from
  edit_spec?: EditSpec | null;
  can_download: boolean;
  expired: boolean;
  asset: DownloadAsset | null;
  shared?: boolean;
  user_email?: string;
}

export interface ShareRecipient {
  id: number;
  email: string;
  display_name: string | null;
}

export interface ShareLink {
  id: number;
  token: string;
  url: string; // path like "/watch/<token>" — prepend window.location.origin to share
  allow_download: boolean;
  has_password: boolean;
  expires_at: string | null;
  view_count: number;
  created_at: string | null;
}

export interface DownloadUsage {
  footprint_bytes: number;
  active_jobs: number;
  running_jobs: number;
  max_bytes: number;
  max_concurrent: number;
  max_jobs: number;
  unlimited: boolean;
}

export interface AdminStorage {
  ready_files: number;
  total_bytes: number;
  total_assets: number;
  total_cap_bytes: number;
  per_user: { user_id: number; email: string | null; footprint_bytes: number }[];
}

export interface AdminDownloadQuota {
  user_id: number;
  custom: boolean;
  max_bytes: number;
  max_concurrent: number;
  max_jobs: number;
  unlimited: boolean;
}

export const downloadsApi = {
  downloadProfiles: (): Promise<DownloadProfile[]> => req("/api/downloads/profiles"),
  createDownloadProfile: (p: { name: string; spec: DownloadSpec }): Promise<DownloadProfile> =>
    req("/api/downloads/profiles", { method: "POST", body: JSON.stringify(p) }),
  updateDownloadProfile: (
    id: number,
    patch: { name?: string; spec?: DownloadSpec },
  ): Promise<DownloadProfile> =>
    req(`/api/downloads/profiles/${id}`, { method: "PATCH", body: JSON.stringify(patch) }),
  deleteDownloadProfile: (id: number) => req(`/api/downloads/profiles/${id}`, { method: "DELETE" }),

  enqueueDownload: (body: {
    source: string;
    profile_id?: number;
    spec?: DownloadSpec;
    display_name?: string;
  }): Promise<DownloadJob> => req("/api/downloads", { method: "POST", body: JSON.stringify(body) }),
  enqueueEdit: (body: {
    source_job_id: number;
    edit_spec: EditSpec;
    display_name?: string;
  }): Promise<DownloadJob> =>
    req("/api/downloads/edit", { method: "POST", body: JSON.stringify(body) }),
  downloadStoryboard: (id: number): Promise<StoryboardMeta> =>
    req(`/api/downloads/${id}/storyboard`),
  downloads: (): Promise<DownloadJob[]> => req("/api/downloads"),
  downloadsShared: (): Promise<DownloadJob[]> => req("/api/downloads/shared"),
  // Remove a shared-with-me item from your list (deletes only your share grant, not the file).
  removeSharedDownload: (jobId: number): Promise<{ removed: number }> =>
    req(`/api/downloads/shared/${jobId}`, { method: "DELETE" }),
  downloadUsage: (): Promise<DownloadUsage> => req("/api/downloads/usage"),
  downloadIndex: (): Promise<Record<string, DownloadStatus>> => req("/api/downloads/index"),
  pauseDownload: (id: number): Promise<DownloadJob> =>
    req(`/api/downloads/${id}/pause`, { method: "POST" }, { idempotent: true }),
  resumeDownload: (id: number): Promise<DownloadJob> =>
    req(`/api/downloads/${id}/resume`, { method: "POST" }, { idempotent: true }),
  cancelDownload: (id: number): Promise<DownloadJob> =>
    req(`/api/downloads/${id}/cancel`, { method: "POST" }, { idempotent: true }),
  // Force a fresh re-download of a finished/failed job — keeps the job (and its share links),
  // deletes the old file, and rebuilds it under the current format/naming rules.
  redownloadDownload: (id: number): Promise<DownloadJob> =>
    req(`/api/downloads/${id}/redownload`, { method: "POST" }, { idempotent: true }),
  deleteDownload: (id: number) => req(`/api/downloads/${id}`, { method: "DELETE" }),
  // Update a download's display metadata (title / channel name+link / extra reference URLs).
  updateDownloadMeta: (
    id: number,
    meta: {
      display_name?: string;
      display_uploader?: string;
      display_uploader_url?: string;
      extra_links?: string[];
    },
  ): Promise<DownloadJob> =>
    req(`/api/downloads/${id}`, { method: "PATCH", body: JSON.stringify(meta) }),
  shareDownload: (id: number, email: string): Promise<{ shared_with: string }> =>
    req(`/api/downloads/${id}/share`, { method: "POST", body: JSON.stringify({ email }) }),
  unshareDownload: (id: number, email: string) =>
    req(`/api/downloads/${id}/share/${encodeURIComponent(email)}`, { method: "DELETE" }),
  // Registered users this user can share with (for the internal picker).
  shareRecipients: (): Promise<ShareRecipient[]> => req("/api/downloads/recipients"),
  // Public watch links (share by link).
  downloadLinks: (jobId: number): Promise<ShareLink[]> => req(`/api/downloads/${jobId}/links`),
  createDownloadLink: (
    jobId: number,
    body: { allow_download?: boolean; expires_days?: number | null; password?: string },
  ): Promise<ShareLink> =>
    req(`/api/downloads/${jobId}/links`, { method: "POST", body: JSON.stringify(body) }),
  updateDownloadLink: (
    linkId: number,
    patch: { allow_download?: boolean; expires_days?: number | null; password?: string },
  ): Promise<ShareLink> =>
    req(`/api/downloads/links/${linkId}`, { method: "PATCH", body: JSON.stringify(patch) }),
  revokeDownloadLink: (linkId: number): Promise<{ deleted: number }> =>
    req(`/api/downloads/links/${linkId}`, { method: "DELETE" }),
  // A plain <a> navigation can't send the X-Siftlode-Account header, so pass the active
  // account via ?account= (same wallet-gated selection the WebSocket uses) — otherwise the
  // download resolves to the session-default account and 404s for a per-tab account's file.
  downloadFileUrl: (id: number): string => {
    const a = getActiveAccount();
    return a != null ? `/api/downloads/${id}/file?account=${a}` : `/api/downloads/${id}/file`;
  },
  // Filmstrip sprite (an <img>/background src → direct nav, so it needs the ?account= wallet gate).
  storyboardImageUrl: (id: number): string => {
    const a = getActiveAccount();
    return a != null
      ? `/api/downloads/${id}/storyboard.jpg?account=${a}`
      : `/api/downloads/${id}/storyboard.jpg`;
  },

  // admin
  adminDownloads: (): Promise<DownloadJob[]> => req("/api/admin/downloads"),
  adminDownloadStorage: (): Promise<AdminStorage> => req("/api/admin/downloads/storage"),
  adminGetDownloadQuota: (userId: number): Promise<AdminDownloadQuota> =>
    req(`/api/admin/downloads/quota/${userId}`),
  adminSetDownloadQuota: (
    userId: number,
    patch: Partial<
      Pick<AdminDownloadQuota, "max_bytes" | "max_concurrent" | "max_jobs" | "unlimited">
    >,
  ): Promise<AdminDownloadQuota> =>
    req(`/api/admin/downloads/quota/${userId}`, { method: "PUT", body: JSON.stringify(patch) }),
  adminResetDownloadQuota: (userId: number): Promise<AdminDownloadQuota> =>
    req(`/api/admin/downloads/quota/${userId}`, { method: "DELETE" }),
};
