"use client";

import { useState } from "react";
import { useRouter } from "next/navigation";

import { Alert, Field, Select, Switch } from "@/components/ui/form";
import { Button } from "@/components/ui/button";
import { apiFetch, ApiRequestError } from "@/lib/client/api";
import { applyTheme } from "@/lib/client/theme";
import { useSession } from "@/components/layout/session-provider";
import type { StreamingProviderDescriptor } from "@/services/providers/types";
import type { UserSettings } from "@/lib/types";

/**
 * Playback and appearance preferences.
 *
 * Theme is applied optimistically and reverted if the write fails, because the
 * visitor is looking at the result of the change and a silent rollback would be
 * more confusing than a brief flash.
 */

/** Editable subset of `UserSettings`, with provider ids as plain strings. */
interface FormState {
  theme: UserSettings["theme"];
  autoplay: boolean;
  reduceMotion: boolean;
  streaming: string;
  download: string;
}

function toForm(settings: UserSettings | null): FormState {
  return {
    theme: settings?.theme ?? "dark",
    autoplay: settings?.autoplayNextEpisode ?? true,
    reduceMotion: settings?.reduceMotion ?? false,
    streaming: settings?.defaultStreamingProvider ?? "",
    download: settings?.defaultDownloadProvider ?? "",
  };
}

/**
 * Drop a stored provider id that is no longer in the directory.
 *
 * A visitor who chose a server before it was removed still has its id in their
 * settings, and a `<select>` whose value matches none of its options renders
 * blank - so the control would show nothing while the account quietly still
 * preferred the dead id, and every unrelated save from this form would write it
 * straight back. Falling back to "Automatic" matches what the player does with
 * the same value anyway: it ignores an id that is not enabled and takes the
 * first server.
 */
function validProviderId(stored: string, available: Array<{ id: string }>): string {
  return stored && available.some((p) => p.id === stored) ? stored : "";
}

export function PlaybackSettingsForm({
  providers,
  downloadProviders,
}: {
  providers: StreamingProviderDescriptor[];
  downloadProviders: StreamingProviderDescriptor[];
}) {
  const router = useRouter();
  const { settings, setSettings } = useSession();

  const [form, setForm] = useState<FormState>(() => toForm(settings));
  const [pending, setPending] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [saved, setSaved] = useState(false);
  const [syncedFrom, setSyncedFrom] = useState(settings);

  /*
    Normalise the stored ids against the directory now that it is known.

    `toForm` runs before the provider list is in scope, so a removed server is
    only detectable here. Adjusting during render rather than in an effect keeps
    this a single pass instead of a second render, and the state converges
    immediately - a visitor with a removed server selected sees "Automatic",
    which is what the player already does with that value.
  */
  if (
    (form.streaming && !validProviderId(form.streaming, providers)) ||
    (form.download && !validProviderId(form.download, downloadProviders))
  ) {
    setForm((prev) => ({
      ...prev,
      streaming: validProviderId(prev.streaming, providers),
      download: validProviderId(prev.download, downloadProviders),
    }));
  }

  // Adopt a server-rendered snapshot that arrives after hydration, adjusting
  // during render rather than in an effect (which would cascade renders).
  if (syncedFrom !== settings) {
    setSyncedFrom(settings);
    setForm(toForm(settings));
  }

  const { theme, autoplay, reduceMotion, streaming, download } = form;

  const update = <K extends keyof FormState>(key: K, value: FormState[K]) =>
    setForm((current) => ({ ...current, [key]: value }));

  const save = async (event: React.FormEvent) => {
    event.preventDefault();
    if (pending) return;

    setPending(true);
    setError(null);
    setSaved(false);

    try {
      const next = await apiFetch<{ settings: UserSettings }>("/api/settings", {
        method: "PATCH",
        body: JSON.stringify({
          theme: form.theme,
          autoplayNextEpisode: form.autoplay,
          reduceMotion: form.reduceMotion,
          defaultStreamingProvider: form.streaming || null,
          defaultDownloadProvider: form.download || null,
        }),
      });

      setSettings(next.settings);
      applyTheme(next.settings.theme);
      document.documentElement.dataset.reduceMotion = String(next.settings.reduceMotion);
      setForm(toForm(next.settings));
      setSaved(true);
      router.refresh();
    } catch (err) {
      setError(err instanceof ApiRequestError ? err.message : "Could not save your settings.");
      // The server is the source of truth, so snap the form back to it.
      if (settings) setForm(toForm(settings));
    } finally {
      setPending(false);
    }
  };

  return (
    <form onSubmit={save} noValidate className="space-y-6">
      {error ? <Alert tone="error">{error}</Alert> : null}
      {saved ? <Alert tone="success">Preferences saved.</Alert> : null}

      <Field label="Appearance" htmlFor="pref-theme" hint="Applies immediately across the site.">
        <Select
          id="pref-theme"
          value={theme}
          onChange={(e) => {
            const next = e.target.value as UserSettings["theme"];
            update("theme", next);
            applyTheme(next);
          }}
          className="sm:max-w-xs"
        >
          <option value="dark">Dark</option>
          <option value="light">Light</option>
        </Select>
      </Field>

      <div className="divide-y divide-line border-y border-line">
        <Switch
          checked={autoplay}
          onChange={(next) => update("autoplay", next)}
          label="Autoplay next episode"
          description="Start the following episode automatically when one finishes."
        />
        <Switch
          checked={reduceMotion}
          onChange={(next) => {
            update("reduceMotion", next);
            document.documentElement.dataset.reduceMotion = String(next);
          }}
          label="Reduce motion"
          description="Minimise transitions and parallax effects."
        />
      </div>

      <Field
        label="Preferred streaming server"
        htmlFor="pref-streaming"
        hint="Used as the first choice on every watch page. The dropdown still works."
      >
        <Select
          id="pref-streaming"
          value={streaming}
          onChange={(e) => update("streaming", e.target.value)}
          className="sm:max-w-xs"
          disabled={providers.length === 0}
        >
          <option value="">Automatic (first available)</option>
          {providers.map((p) => (
            <option key={p.id} value={p.id}>
              {p.name}
              {p.badge ? ` — ${p.badge}` : ""}
            </option>
          ))}
        </Select>
      </Field>

      <Field
        label="Preferred download destination"
        htmlFor="pref-download"
        hint="FlixTV only opens the destination you configure — it never re-signs or proxies a file."
      >
        <Select
          id="pref-download"
          value={download}
          onChange={(e) => update("download", e.target.value)}
          className="sm:max-w-xs"
          disabled={downloadProviders.length === 0}
        >
          <option value="">Automatic (first available)</option>
          {downloadProviders.map((p) => (
            <option key={p.id} value={p.id}>
              {p.name}
              {p.badge ? ` — ${p.badge}` : ""}
            </option>
          ))}
        </Select>
      </Field>

      <Button type="submit" loading={pending}>
        Save preferences
      </Button>
    </form>
  );
}
