import { mkdirSync, writeFileSync } from "node:fs";
import { join, dirname } from "node:path";
import { fileURLToPath } from "node:url";
import React from "react";
import { render } from "ink-testing-library";
import { Box, Text } from "ink";
import { StoreContext, type Store } from "../src/ui/store";
import { COLOR, ICON, SOURCE_STYLE } from "../src/ui/theme";
import { Logo } from "../src/ui/components/Logo";
import { Rule } from "../src/ui/components/Rule";
import { Footer } from "../src/ui/components/Footer";
import { Sidebar, RAIL_WIDTH } from "../src/ui/components/Sidebar";
import { SearchBar } from "../src/ui/components/SearchBar";
import { Panel } from "../src/ui/components/Panel";
import { Downloads } from "../src/ui/components/Downloads";
import { footerHints } from "../src/ui/keymap";
import { sourcesByGroup } from "../src/sources/registry";
import { cleanText, formatBytes, formatRelative } from "../src/util/format";
import { ansiToSvg } from "./ansi-to-svg";
import type { Config } from "../src/config/config";
import type { DownloadQueue } from "../src/download/queue";
import type { QueueItem } from "../src/download/types";
import type { HistoryItem } from "../src/download/history";
import type { TorrentResult } from "../src/sources/types";

const COLS = 80;
const CONTENT_WIDTH = Math.max(24, COLS - RAIL_WIDTH - 3);
const RULE_WIDTH = Math.max(10, COLS - 2);
const OUT_DIR = join(dirname(fileURLToPath(import.meta.url)), "..", "preview");
mkdirSync(OUT_DIR, { recursive: true });

const NOW = Math.floor(Date.now() / 1000);
const NOW_MS = Date.now();

const RESULTS: TorrentResult[] = [
  { infoHash: "b2", name: "Oppenheimer (2023) [1080p WEB]", source: "yts", sizeBytes: 2.1e9, seeders: 1240, leechers: 88, magnet: "", added: NOW - 7200 },
  { infoHash: "g7", name: "Dune: Part Two (2024) [2160p BluRay]", source: "yts", sizeBytes: 8.4e9, seeders: 910, leechers: 41, magnet: "", added: NOW - 90000 },
  { infoHash: "c3", name: "Breaking Bad S05E14 1080p WEB-DL", source: "eztv", sizeBytes: 1.6e9, seeders: 540, leechers: 31, magnet: "", added: NOW - 1800 },
  { infoHash: "e5", name: "[Erai-raws] Jujutsu Kaisen S2 - 23 [1080p]", source: "nyaa", sizeBytes: 1.3e9, seeders: 320, leechers: 12, magnet: "", added: NOW - 900 },
  { infoHash: "d4", name: "Frieren - 28 [1080p]", source: "subsplease", sizeBytes: 1.4e9, seeders: 0, leechers: 0, magnet: "", added: NOW - 600 },
  { infoHash: "a1", name: "Elden Ring: Shadow of the Erdtree Edition", source: "fitgirl", sizeBytes: 0, seeders: 0, leechers: 0, magnet: "", added: NOW - 3600 },
];

const DOWNLOADS: QueueItem[] = [
  { id: "x1", name: "Dune: Part Two (2024) [2160p BluRay]", source: "yts", magnet: "", dir: "", status: "downloading", progress: 64, totalBytes: 8.4e9, downloadedBytes: 5.4e9, speed: 8.1e6, peers: 41, eta: 360, addedAt: NOW_MS },
];

const HISTORY: HistoryItem[] = [
  { id: "h1", name: "Elden Ring: Shadow of the Erdtree Edition", source: "fitgirl", sizeBytes: 54e9, magnet: "", dir: "", completedAt: NOW_MS - 3_600_000 },
  { id: "h2", name: "Breaking Bad S05E14 1080p WEB-DL", source: "eztv", sizeBytes: 1.6e9, magnet: "", dir: "", completedAt: NOW_MS - 90_000_000 },
];

function fakeQueue(items: QueueItem[], history: HistoryItem[]): DownloadQueue {
  const active = items.filter((i) => i.status === "downloading").length;
  const stub = {
    getItems: () => items,
    getHistory: () => history,
    activeCount: active,
    on: () => stub,
    off: () => stub,
  };
  return stub as unknown as DownloadQueue;
}

function makeStore(
  overrides: Partial<Store> = {},
  items: QueueItem[] = [],
  history: HistoryItem[] = [],
): Store {
  const noop = (): void => {};
  return {
    config: { downloadDir: "~/Downloads/torlink" } as Config,
    setConfig: noop,
    queue: fakeQueue(items, history),
    view: "browser",
    setView: noop,
    query: "",
    submitQuery: noop,
    section: "all",
    setSection: noop,
    region: "content",
    setRegion: noop,
    captureMode: "none",
    setCaptureMode: noop,
    downloadFocus: null,
    setDownloadFocus: noop,
    startDownload: noop,
    notice: null,
    setNotice: noop,
    quitAll: noop,
    listRows: 14,
    compact: false,
    contentWidth: CONTENT_WIDTH,
    cols: COLS,
    rows: 24,
    ...overrides,
  };
}

function save(name: string, store: Store, node: React.ReactNode): void {
  const { lastFrame, unmount } = render(
    <StoreContext.Provider value={store}>{node}</StoreContext.Provider>,
  );
  const frame = lastFrame() ?? "";
  unmount();
  if (!/\x1b\[/.test(frame)) {
    throw new Error(`${name}: frame has no ANSI colors (FORCE_COLOR didn't take)`);
  }
  writeFileSync(join(OUT_DIR, `${name}.svg`), ansiToSvg(frame, { cols: COLS, title: "torlink" }));
  console.log(`preview/${name}.svg`);
}

const CATEGORIES = sourcesByGroup()
  .map((g) => g.group.toLowerCase())
  .join(`  ${ICON.dot}  `);

save(
  "splash",
  makeStore({ view: "splash", region: "content" }),
  <Box height={18} flexDirection="column" justifyContent="center" alignItems="center" width={COLS}>
    <Logo />
    <Box marginTop={2}>
      <Text color={COLOR.text}>A curated, terminal-native torrent downloader.</Text>
    </Box>
    <Box>
      <Text dimColor>{CATEGORIES}</Text>
    </Box>
    <Box marginTop={1} width={62}>
      <SearchBar width={62} value="" editing placeholder="Search, or paste a magnet…" onSubmit={() => {}} />
    </Box>
    <Box marginTop={1}>
      <Text>
        <Text color={COLOR.alt}>↵</Text>
        <Text dimColor> search</Text>
        <Text dimColor>{`  ${ICON.dot}  `}</Text>
        <Text dimColor>empty </Text>
        <Text color={COLOR.alt}>↵</Text>
        <Text dimColor> browse</Text>
        <Text dimColor>{`  ${ICON.dot}  `}</Text>
        <Text color={COLOR.alt}>^c</Text>
        <Text dimColor> quit</Text>
      </Text>
    </Box>
  </Box>,
);

const browseResults = RESULTS.slice(0, 5);
const showStats = browseResults.some((r) => r.sizeBytes > 0 || r.seeders > 0);
const numW = Math.max(2, String(browseResults.length).length);

save(
  "browse",
  makeStore({ section: "all", contentWidth: CONTENT_WIDTH, listRows: 14, cols: COLS, rows: 24 }),
  <Box flexDirection="column" width={COLS} paddingX={1}>
    <Box justifyContent="space-between">
      <Logo />
    </Box>
    <Rule width={RULE_WIDTH} />
    <Box height={14} marginTop={1}>
      <Sidebar />
      <Box flexGrow={1} flexDirection="column">
        <SearchBar width={CONTENT_WIDTH} value="" editing={false} placeholder="Search, or paste a magnet…" onSubmit={() => {}} />
        <Box marginTop={1}>
          <Panel title="latest" width={CONTENT_WIDTH} focused count={`(${browseResults.length})`} height={9}>
            <Box><Text dimColor>newest across all sources</Text></Box>
            <Box flexDirection="column" marginTop={1}>
              <Box>
                <Box width={2} flexShrink={0} />
                <Box width={numW} flexShrink={0} justifyContent="flex-end"><Text bold dimColor>#</Text></Box>
                <Box flexGrow={1} minWidth={0} marginLeft={1}><Text bold dimColor>Name</Text></Box>
                <Box width={10} flexShrink={0} marginLeft={1} justifyContent="flex-end"><Text bold dimColor>Size</Text></Box>
                <Box width={9} flexShrink={0} marginLeft={1} justifyContent="flex-end"><Text bold dimColor>Seed:Lch</Text></Box>
                <Box width={4} flexShrink={0} marginLeft={1} justifyContent="flex-end"><Text bold dimColor>Src</Text></Box>
              </Box>
              {browseResults.map((r, i) => {
                const here = i === 0;
                const ss = SOURCE_STYLE[r.source];
                return (
                  <Box key={r.infoHash}>
                    <Box width={2} flexShrink={0}>
                      <Text color={COLOR.accent}>{here ? ICON.pointer : ""}</Text>
                    </Box>
                    <Box width={numW} flexShrink={0} justifyContent="flex-end">
                      <Text dimColor>{i + 1}</Text>
                    </Box>
                    <Box flexGrow={1} minWidth={0} marginLeft={1}>
                      <Text wrap="truncate-end" color={here ? COLOR.accent : undefined} dimColor={!here} bold={here}>
                        {cleanText(r.name)}
                      </Text>
                    </Box>
                    {showStats ? (
                      <>
                        <Box width={10} flexShrink={0} marginLeft={1} justifyContent="flex-end">
                          <Text dimColor>{r.sizeBytes > 0 ? formatBytes(r.sizeBytes) : "-"}</Text>
                        </Box>
                        <Box width={9} flexShrink={0} marginLeft={1} justifyContent="flex-end">
                          <Text color={r.seeders > 0 ? COLOR.good : undefined} dimColor={r.seeders === 0}>
                            {r.seeders || r.leechers ? `${r.seeders}:${r.leechers}` : "-"}
                          </Text>
                        </Box>
                      </>
                    ) : (
                      <Box width={9} flexShrink={0} marginLeft={1} justifyContent="flex-end">
                        <Text dimColor>{formatRelative(r.added) || "-"}</Text>
                      </Box>
                    )}
                    <Box width={4} flexShrink={0} marginLeft={1} justifyContent="flex-end">
                      <Text color={ss.color} dimColor={!here}>
                        {ss.tag}
                      </Text>
                    </Box>
                  </Box>
                );
              })}
            </Box>
          </Panel>
        </Box>
      </Box>
    </Box>
    <Footer hints={footerHints("content", "all")} />
  </Box>,
);

save(
  "downloads",
  makeStore({ section: "downloads", contentWidth: CONTENT_WIDTH, listRows: 10, cols: COLS, rows: 24 }, DOWNLOADS, HISTORY),
  <Box flexDirection="column" width={COLS} paddingX={1}>
    <Box justifyContent="space-between">
      <Logo />
    </Box>
    <Rule width={RULE_WIDTH} />
    <Box height={10} marginTop={1}>
      <Sidebar />
      <Box flexGrow={1} flexDirection="column">
        <Downloads />
      </Box>
    </Box>
    <Footer hints={footerHints("content", "downloads")} />
  </Box>,
);
