import { invoke, convertFileSrc } from "@tauri-apps/api/core";
import { listen, type UnlistenFn } from "@tauri-apps/api/event";
import { open } from "@tauri-apps/plugin-dialog";
import type {
  ChannelWithCount,
  DownloadProgressEvent,
  DownloadStateEvent,
  FeedFilter,
  FeedItem,
  ImportPreview,
  Prereqs,
  Quality,
  RefreshProgress,
  RefreshSummary,
  Stream,
  UpdateStatus,
} from "./types";

export const checkPrereqs = () => invoke<Prereqs>("check_prereqs");

export const listChannels = () => invoke<ChannelWithCount[]>("list_channels");

export const listFeed = (filter: FeedFilter) =>
  invoke<FeedItem[]>("list_feed", { filter });

export const refreshFeeds = () => invoke<RefreshSummary>("refresh_feeds");

export const downloadVideo = (videoId: string, quality: Quality, subLang: string) =>
  invoke<void>("download_video", { videoId, quality, subLang });

/** Downloads still unfinished from a previous run, as (id, quality, subLang). */
export const interruptedDownloads = () =>
  invoke<Array<[string, string, string]>>("interrupted_downloads");

/** Stops everything downloading or waiting to. Returns how many were stopped. */
export const cancelAllDownloads = () => invoke<number>("cancel_all_downloads");

export const deleteAllDownloads = () => invoke<number>("delete_all_downloads");

/** Fills in missing video lengths, a batch at a time. Returns how many. */
/** Fills lengths for the videos on screen first. */
export const fetchDurations = (visible: string[]) =>
  invoke<number>("fetch_durations", { visible });

/** Browsers installed here that yt-dlp can read cookies from: [id, label]. */
export const listBrowsers = () => invoke<Array<[string, string]>>("list_browsers");

/** Empty string signs out. */
export const setCookieSource = (browser: string) =>
  invoke<void>("set_cookie_source", { browser });

/** Resolves a known video to check whether YouTube is currently reachable. */
export const testYoutube = () => invoke<string>("test_youtube");

export const checkYtDlpUpdate = () => invoke<UpdateStatus>("check_yt_dlp_update");

/** Downloads the newest yt-dlp and switches to it. Returns its version. */
export const updateYtDlp = () => invoke<string>("update_yt_dlp");

/** WebVTT sidecars beside a downloaded video, as [language, path] pairs. */
export interface ScrapeResult {
  channels: Array<{ id: string; title: string; url: string }>;
  unresolved: string[];
  looked_up: number;
}

/** Reads the subscription list out of a signed-in browser page. */
export const scrapeSubscriptions = () => invoke<ScrapeResult>("scrape_subscriptions");

export const previewScrapedImport = (channels: ScrapeResult["channels"]) =>
  invoke<ImportPreview>("preview_scraped_import", { channels });

export const importScraped = (channels: ScrapeResult["channels"]) =>
  invoke<number>("import_scraped", { channels });

/** What emptying the subscription list would take with it. */
export const previewRemoveAllSubscriptions = () =>
  invoke<ImportPreview>("preview_remove_all_subscriptions");

export const removeAllSubscriptions = () => invoke<number>("remove_all_subscriptions");

/** The menu bar panel's own actions. */
export const traySaveVideo = () => invoke<void>("tray_save_video");
export const showMainWindow = () => invoke<void>("show_main_window");
export const hidePanel = () => invoke<void>("hide_panel");
export const quitApp = () => invoke<void>("quit_app");

/** Adds one channel from any YouTube link. Returns its title. */
export const addChannel = (url: string) => invoke<string>("add_channel", { url });

export interface RemovalPreview {
  title: string;
  videos: number;
  downloaded: number;
}

export const previewDeleteChannel = (channelId: string) =>
  invoke<RemovalPreview>("preview_delete_channel", { channelId });

/** Unsubscribes in the app only — nothing changes on YouTube. */
export const deleteChannel = (channelId: string) =>
  invoke<void>("delete_channel", { channelId });

/** Keeps the menu bar's downloads matching the window's settings. */
export const setDownloadDefaults = (quality: string, subLang: string) =>
  invoke<void>("set_download_defaults", { quality, subLang });

export const listSubtitles = (videoId: string) =>
  invoke<Array<[string, string]>>("list_subtitles", { videoId });

/** Subtitles read out of a downloaded file, as (language, WebVTT text). */
export const embeddedSubtitles = (videoId: string) =>
  invoke<Array<[string, string]>>("embedded_subtitles", { videoId });

/** Subtitles as (language, WebVTT text) — fetched and cached when there are
 *  none beside the video. Streaming has no other source. */
export const fetchSubtitles = (videoId: string, lang: string) =>
  invoke<Array<[string, string]>>("fetch_subtitles", { videoId, lang });

export const savePlayback = (videoId: string, position: number, duration: number) =>
  invoke<void>("save_playback", { videoId, position, duration });

export const cancelDownload = (videoId: string) =>
  invoke<void>("cancel_download", { videoId });

export const deleteDownload = (videoId: string) =>
  invoke<void>("delete_download", { videoId });

export const getConnectivity = () => invoke<boolean>("get_connectivity");

/**
 * A directly playable URL for an undownloaded video. When a quality cap is set
 * the backend serves a rewritten playlist from its own loopback server, so this
 * is always a plain URL either way.
 */
export async function resolveStream(
  videoId: string,
  maxHeight: number | null,
): Promise<string> {
  const s = await invoke<Stream>("resolve_stream", { videoId, maxHeight });
  if (!s.url) throw new Error("No playable stream was returned.");
  return s.url;
}

export const setLibraryPath = (path: string) =>
  invoke<string>("set_library_path", { path });

export const openExternal = (url: string) =>
  invoke<void>("open_external", { url });

/** Opens the native file picker for a Takeout subscriptions.csv. */
export async function pickTakeoutFile(): Promise<string | null> {
  const path = await open({
    multiple: false,
    directory: false,
    filters: [{ name: "Takeout subscriptions", extensions: ["csv"] }],
  });
  return typeof path === "string" ? path : null;
}

export const previewTakeoutImport = (path: string) =>
  invoke<ImportPreview>("preview_takeout_import", { path });

/** Replaces the whole subscription list. Confirm with the user first. */
export const importTakeoutCsv = (path: string) =>
  invoke<number>("import_takeout_csv", { path });

export async function pickLibraryFolder(): Promise<string | null> {
  const path = await open({ directory: true, multiple: false });
  if (typeof path !== "string") return null;
  return setLibraryPath(path);
}

export const onRefreshProgress = (cb: (p: RefreshProgress) => void) =>
  listen<RefreshProgress>("refresh:progress", (e) => cb(e.payload));

export const onDownloadProgress = (cb: (p: DownloadProgressEvent) => void) =>
  listen<DownloadProgressEvent>("download:progress", (e) => cb(e.payload));

export const onDownloadState = (cb: (p: DownloadStateEvent) => void) =>
  listen<DownloadStateEvent>("download:state", (e) => cb(e.payload));

export type { UnlistenFn };

/** Local file path -> a URL the webview is allowed to load. */
export const fileUrl = (path: string) => convertFileSrc(path);

/**
 * Prefers the on-disk thumbnail so the feed still renders with no network,
 * falling back to the remote URL for videos not yet cached.
 */
export const thumbSrc = (item: FeedItem, online: boolean) => {
  if (item.thumb_path) return convertFileSrc(item.thumb_path);
  return online ? item.thumb_url : "";
};
