import { P, match } from "ts-pattern";
import {
  createContext,
  PropsWithChildren,
  useContext,
  useEffect,
  useState,
} from "react";
import DragBar from "@/components/drag-bar";
import { Progress } from "@/components/ui/progress";
import { listen } from "@tauri-apps/api/event";
import Background from "@/assets/background.jpg";

const prettyBytes = (num: number, precision = 3, addSpace = true) => {
  const UNITS = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
  if (Math.abs(num) < 1) return num + (addSpace ? " " : "") + UNITS[0];
  const exponent = Math.min(
    Math.floor(Math.log10(num < 0 ? -num : num) / 3),
    UNITS.length - 1
  );
  const n = Number(
    ((num < 0 ? -num : num) / 1000 ** exponent).toPrecision(precision)
  );
  return (num < 0 ? "-" : "") + n + (addSpace ? " " : "") + UNITS[exponent];
};

const PatcherProgress = () => {
  const state = useStatus();

  return (
    <div className="px-8 py-4 text-sm self-center">
      {match(state)
        .with({ error: P.not(P.nullish) }, ({ error }) => (
          <pre>{JSON.stringify(error)}</pre>
        ))
        .with({ status: Status.Awaiting }, () => (
          <div className="animate-pulse">Starting patcher...</div>
        ))
        .with({ status: Status.Fetching }, () => (
          <div className="animate-pulse">Fetching checksums...</div>
        ))
        .with({ status: Status.Checksumming }, () => (
          <div className="animate-pulse">Checking local files...</div>
        ))
        .with({ status: Status.Verifying }, () => (
          <div className="animate-pulse">Verifying files...</div>
        ))
        .with({ status: Status.Updating }, ({ download }) => (
          <div>
            <div className="mb-2 animate-pulse">Updating files...</div>
            {download && (
              <div className="grid gap-1.5">
                <Progress value={download.percent} />
                <span
                  className="text-xs opacity-75
                "
                >
                  {download.file}: {prettyBytes(download.downloaded)}/
                  {prettyBytes(download.total)}
                </span>
              </div>
            )}
          </div>
        ))
        .otherwise(() => (
          <div className="text-primary brightness-150">Ready!</div>
        ))}
    </div>
  );
};

enum Status {
  Awaiting,
  Fetching,
  Checksumming,
  Verifying,
  Updating,
  Ready,
}

type Download = {
  file: string;
  downloaded: number;
  total: number;
  percent: number;
};

const stateContext = createContext<{
  status: Status;
  error: string | null;
  download: Download | null;
}>({
  status: Status.Awaiting,
  error: null,
  download: null,
});

const StateProvider = ({ children }: PropsWithChildren) => {
  const [status, setStatus] = useState<Status>(Status.Awaiting);
  const [error, setError] = useState<string | null>(null);
  const [download, setDownload] = useState<Download | null>(null);

  useEffect(() => {
    const setupListeners = async () => {
      const statusUnlisten = await listen<Status>("status", (event) => {
        setStatus(event.payload);
      });
      const errorUnlisten = await listen<string>("error", (event) => {
        setError(event.payload);
      });
      const downloadUnlisten = await listen<Download>("download", (event) => {
        setDownload(event.payload);
      });

      return () => {
        statusUnlisten();
        errorUnlisten();
        downloadUnlisten();
      };
    };

    let cleanup: () => void;
    setupListeners().then((cleanupFn) => {
      cleanup = cleanupFn;
    });

    return () => {
      if (cleanup) cleanup();
    };
  }, []);

  return (
    <stateContext.Provider value={{ status, error, download }}>
      {children}
    </stateContext.Provider>
  );
};

const useStatus = () => useContext(stateContext);

const Patcher = () => {
  const { status, error } = useStatus();

  return (
    <>
      <img
        src={Background}
        alt="Background"
        className="absolute inset-0 size-full object-center object-cover"
      />
      <div className="grid grid-cols-[1fr_auto] items-stretch border-t border-primary/10 absolute bottom-0 inset-x-0 z-10 bg-background/90 backdrop-blur-2xl">
        <PatcherProgress />
        <button
          type="button"
          className="px-12 py-8 bg-linear-to-tr from-primary/80 to-primary/50 font-semibold text-sm hover:brightness-125 transition-all disabled:grayscale-75 disabled:opacity-50"
          disabled={status !== Status.Ready || !!error}
        >
          Start now
        </button>
      </div>
    </>
  );
};

function App() {
  return (
    <StateProvider>
      <main className="w-screen h-screen relative overflow-hidden">
        <DragBar isMain />
        <Patcher />
      </main>
    </StateProvider>
  );
}

export default App;
