"use client";

import { useEffect, useState } from "react";
import { Check, Loader2 } from "lucide-react";
import clsx from "clsx";

export type Field =
  | { key: string; label: string; hint?: string; type: "toggle" }
  | { key: string; label: string; hint?: string; type: "number"; min?: number; max?: number; ownedBy?: string }
  | { key: string; label: string; hint?: string; type: "text"; placeholder?: string; ownedBy?: string }
  | { key: string; label: string; hint?: string; type: "select"; options: string[]; ownedBy?: string };

/**
 * A settings form built from a field list. Every settings page needs the same
 * load, edit, save cycle, so describing the fields is enough.
 */
export function SettingsForm({
  endpoint = "/api/admin/settings",
  title,
  intro,
  fields,
}: {
  endpoint?: string;
  title: string;
  intro?: string;
  fields: Field[];
}) {
  const [values, setValues] = useState<Record<string, unknown> | null>(null);
  const [saving, setSaving] = useState(false);
  const [note, setNote] = useState<string | null>(null);

  useEffect(() => {
    fetch(endpoint)
      .then((r) => (r.ok ? r.json() : null))
      .then((d) => setValues(d ?? {}))
      .catch(() => setValues({}));
  }, [endpoint]);

  if (!values) return <p className="ps-hint">Loading…</p>;

  const set = (key: string, value: unknown) => setValues({ ...values, [key]: value });

  const toggles = fields.filter((f) => f.type === "toggle");
  // Cards with a description line need more width, so three per row not four.
  const anyDescription = toggles.some((f) => f.hint);
  // Anything paired to a toggle renders inside that card, not on its own.
  const owned = new Set(
    fields.flatMap((f) => ("ownedBy" in f && f.ownedBy ? [f.key] : []))
  );

  async function save() {
    setSaving(true);
    // Only the fields on this page, so one form can't clobber another's values.
    const patch = Object.fromEntries(fields.map((f) => [f.key, values![f.key]]));
    await fetch(endpoint, {
      method: "PATCH",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(patch),
    }).catch(() => {});
    setSaving(false);
    setNote("Saved");
    setTimeout(() => setNote(null), 2400);
  }

  return (
    <section className="ps-panel">
      <div className="ps-panelhead">
        <h3 className="an-title">{title}</h3>
        <button onClick={save} disabled={saving} className="ab-save-top">
          {saving ? <Loader2 size={15} className="animate-spin" /> : <Check size={15} />}
          Save
        </button>
      </div>
      {intro && <p className="ps-hint">{intro}</p>}

      {/* Toggles as a grid. A card with a description needs more room, and
          one owning a control takes the whole row with the control inside. */}
      {toggles.length > 0 && (
        <div className={clsx("toggle-grid", anyDescription && "wide")}>
          {toggles.map((f) => {
            const owned = fields.filter(
              (x) => "ownedBy" in x && x.ownedBy === f.key
            );

            const row = (
              <button
                onClick={() => set(f.key, !values[f.key])}
                className="cm-toggle-row"
              >
                <span className="min-w-0 text-left">
                  <b>{f.label}</b>
                  {f.hint && <em>{f.hint}</em>}
                </span>
                <span className={clsx("mg-toggle", Boolean(values[f.key]) && "on")}>
                  <i />
                </span>
              </button>
            );

            if (owned.length === 0) {
              return <div key={f.key} className="contents">{row}</div>;
            }

            return (
              <div key={f.key} className="toggle-card-extra">
                {row}
                {owned.map((o) => (
                  <label key={o.key} className="ps-field">
                    <span>{o.label}</span>
                    {o.type === "select" ? (
                      <select
                        value={String(values[o.key] ?? "")}
                        onChange={(e) => set(o.key, e.target.value)}
                      >
                        {o.options.map((opt) => (
                          <option key={opt}>{opt}</option>
                        ))}
                      </select>
                    ) : (
                      <input
                        type={o.type === "number" ? "number" : "text"}
                        value={String(values[o.key] ?? "")}
                        placeholder={"placeholder" in o ? o.placeholder : undefined}
                        onChange={(e) =>
                          set(o.key, o.type === "number" ? Number(e.target.value) : e.target.value)
                        }
                      />
                    )}
                    {o.hint && <i className="sf-hint">{o.hint}</i>}
                  </label>
                ))}
              </div>
            );
          })}
        </div>
      )}

      <div className="sf-fields">
        {fields.filter((f) => f.type !== "toggle" && !owned.has(f.key)).map((f) => {
          return (
            <label key={f.key} className="ps-field">
              <span>{f.label}</span>
              {f.type === "select" ? (
                <select
                  value={String(values[f.key] ?? "")}
                  onChange={(e) => set(f.key, e.target.value)}
                >
                  {f.options.map((o) => (
                    <option key={o}>{o}</option>
                  ))}
                </select>
              ) : (
                <input
                  type={f.type === "number" ? "number" : "text"}
                  value={String(values[f.key] ?? "")}
                  placeholder={"placeholder" in f ? f.placeholder : undefined}
                  onChange={(e) =>
                    set(f.key, f.type === "number" ? Number(e.target.value) : e.target.value)
                  }
                />
              )}
              {f.hint && <i className="sf-hint">{f.hint}</i>}
            </label>
          );
        })}
      </div>

      <button onClick={save} disabled={saving} className="ps-save">
        {saving ? <Loader2 size={15} className="animate-spin" /> : <Check size={15} />}
        Save changes
      </button>

      {note && <div className="ps-note">{note}</div>}
    </section>
  );
}
