"use client";

import { useEffect, useState } from "react";
import { useTheme } from "@/hooks/useTheme";
import { THEMES, MODES } from "@/lib/themes";
import { downloadExport } from "@/lib/export";
import { uploadImport } from "@/lib/import";
import type { SourceState } from "@/lib/types";
import type { Settings } from "@/lib/schemas/setting";

const cardStyle = {
  background: "var(--bg-card)",
  border: "1px solid var(--border)",
  borderRadius: 8,
};
const inputStyle = {
  background: "var(--bg-elevated)",
  color: "var(--text)",
  border: "1px solid var(--border-strong)",
  borderRadius: 6,
};

function Section({ title, children }: { title: string; children: React.ReactNode }) {
  return (
    <section className="p-5 space-y-4" style={cardStyle}>
      <h2 className="text-sm uppercase tracking-widest" style={{ fontFamily: "var(--font-display)", color: "var(--text-muted)" }}>
        {title}
      </h2>
      {children}
    </section>
  );
}

export function SettingsView() {
  const { theme, mode, setTheme, setMode } = useTheme();
  const [settings, setSettings] = useState<Settings | null>(null);
  const [sources, setSources] = useState<SourceState[]>([]);

  async function load() {
    const [s, src] = await Promise.all([
      fetch("/api/settings").then((r) => r.json()),
      fetch("/api/sources").then((r) => r.json()),
    ]);
    setSettings(s.settings);
    setSources(src.sources);
  }
  useEffect(() => {
    void load();
  }, []);

  async function saveSettings(patch: Partial<Settings>) {
    const res = await fetch("/api/settings", {
      method: "PUT",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(patch),
    });
    if (res.ok) setSettings((await res.json()).settings);
  }

  async function patchSource(id: string, patch: Record<string, unknown>) {
    await fetch(`/api/sources/${id}`, {
      method: "PATCH",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(patch),
    });
    await load();
  }

  async function move(index: number, dir: -1 | 1) {
    const a = sources[index];
    const b = sources[index + dir];
    if (!a || !b) return;
    await Promise.all([
      patchSource(a.source.id, { position: b.source.position }),
      patchSource(b.source.id, { position: a.source.position }),
    ]);
  }

  return (
    <main className="max-w-3xl mx-auto px-6 py-8 space-y-6">
      <h1 className="text-3xl" style={{ fontFamily: "var(--font-display)" }}>
        Settings
      </h1>

      <Section title="Appearance">
        <Field label="Theme">
          <div className="flex gap-2">
            {THEMES.map((t) => (
              <Toggle key={t} active={theme === t} onClick={() => setTheme(t)}>{t}</Toggle>
            ))}
          </div>
        </Field>
        <Field label="Mode">
          <div className="flex gap-2">
            {MODES.map((m) => (
              <Toggle key={m} active={mode === m} onClick={() => setMode(m)}>{m}</Toggle>
            ))}
          </div>
        </Field>
      </Section>

      {settings && (
        <Section title="Location & units">
          <GeoSearch
            current={settings.location?.label ?? null}
            onPick={(loc) => saveSettings({ location: loc })}
          />
          <div className="grid grid-cols-3 gap-3">
            <Field label="Label">
              <input
                style={inputStyle}
                className="w-full px-3 py-2 text-sm"
                defaultValue={settings.location?.label ?? ""}
                onBlur={(e) =>
                  saveSettings({
                    location: {
                      label: e.target.value,
                      lat: settings.location?.lat ?? 0,
                      lon: settings.location?.lon ?? 0,
                    },
                  })
                }
              />
            </Field>
            <Field label="Latitude">
              <input
                style={inputStyle}
                className="w-full px-3 py-2 text-sm"
                type="number"
                step="0.0001"
                defaultValue={settings.location?.lat ?? ""}
                onBlur={(e) =>
                  saveSettings({
                    location: {
                      label: settings.location?.label ?? "Home",
                      lat: Number(e.target.value),
                      lon: settings.location?.lon ?? 0,
                    },
                  })
                }
              />
            </Field>
            <Field label="Longitude">
              <input
                style={inputStyle}
                className="w-full px-3 py-2 text-sm"
                type="number"
                step="0.0001"
                defaultValue={settings.location?.lon ?? ""}
                onBlur={(e) =>
                  saveSettings({
                    location: {
                      label: settings.location?.label ?? "Home",
                      lat: settings.location?.lat ?? 0,
                      lon: Number(e.target.value),
                    },
                  })
                }
              />
            </Field>
          </div>
          <div className="flex items-center gap-3">
            <button
              className="px-3 py-2 text-sm"
              style={inputStyle}
              onClick={() =>
                navigator.geolocation?.getCurrentPosition((pos) =>
                  saveSettings({
                    location: {
                      label: settings.location?.label || "Home",
                      lat: Number(pos.coords.latitude.toFixed(4)),
                      lon: Number(pos.coords.longitude.toFixed(4)),
                    },
                  }),
                )
              }
            >
              Use my location
            </button>
            <div className="flex gap-2">
              {(["imperial", "metric"] as const).map((u) => (
                <Toggle key={u} active={settings.units === u} onClick={() => saveSettings({ units: u })}>
                  {u}
                </Toggle>
              ))}
            </div>
          </div>
        </Section>
      )}

      <Section title="Sources">
        <div className="space-y-3">
          {sources.map((it, i) => (
            <SourceRow
              key={it.source.id}
              state={it}
              onPatch={(patch) => patchSource(it.source.id, patch)}
              onUp={i > 0 ? () => move(i, -1) : undefined}
              onDown={i < sources.length - 1 ? () => move(i, 1) : undefined}
            />
          ))}
        </div>
        <p className="mt-3 text-xs" style={{ color: "var(--text-faint)" }}>
          Uncheck a card to hide it from the dashboard. Every source stays available — nothing
          is ever removed. Drag to reorder and resize cards from the dashboard&rsquo;s
          &ldquo;Edit layout&rdquo; mode.
        </p>
      </Section>

      <Section title="Data">
        <div className="flex gap-2">
          <button className="px-3 py-2 text-sm" style={inputStyle} onClick={() => void downloadExport()}>
            Export JSON
          </button>
          <label className="px-3 py-2 text-sm cursor-pointer" style={inputStyle}>
            Import JSON
            <input
              type="file"
              accept="application/json"
              hidden
              onChange={async (e) => {
                const f = e.target.files?.[0];
                if (!f) return;
                try {
                  await uploadImport(f);
                  await load();
                  alert("Import complete.");
                } catch (err) {
                  alert(`Import failed: ${err instanceof Error ? err.message : String(err)}`);
                }
              }}
            />
          </label>
        </div>
      </Section>
    </main>
  );
}

function SourceRow({
  state,
  onPatch,
  onUp,
  onDown,
}: {
  state: SourceState;
  onPatch: (patch: Record<string, unknown>) => void;
  onUp?: () => void;
  onDown?: () => void;
}) {
  const { source } = state;
  const [open, setOpen] = useState(false);
  const configurable =
    source.kind === "news" ||
    source.kind === "feeds" ||
    source.kind === "obsidian" ||
    source.kind === "calendar" ||
    source.kind === "sports" ||
    source.kind === "links" ||
    source.kind === "markets" ||
    source.kind === "hackernews" ||
    source.kind === "uptime";

  return (
    <div className="p-3" style={{ border: "1px solid var(--border)", borderRadius: 6 }}>
      <div className="flex items-center gap-3">
        <input
          type="checkbox"
          checked={source.enabled}
          onChange={(e) => onPatch({ enabled: e.target.checked })}
          aria-label={`Enable ${source.label}`}
        />
        <span className="flex-1 text-sm font-medium">{source.label}</span>
        <span className="text-xs" style={{ color: "var(--text-faint)" }}>
          {state.hasModule ? (state.configured ? "ready" : "needs config") : "local"}
        </span>
        <RefreshControl
          seconds={source.refreshSeconds}
          onChange={(s) => onPatch({ refreshSeconds: s })}
        />
        <button className="text-xs px-1" style={{ color: "var(--text-muted)" }} onClick={onUp} disabled={!onUp}>↑</button>
        <button className="text-xs px-1" style={{ color: "var(--text-muted)" }} onClick={onDown} disabled={!onDown}>↓</button>
        {configurable && (
          <button
            className="text-xs px-2 py-0.5 font-medium"
            style={{
              background: open ? "var(--accent)" : "var(--bg-elevated)",
              color: open ? "var(--text-on-accent)" : "var(--text-accent)",
              border: "1px solid var(--accent)",
              borderRadius: 6,
            }}
            onClick={() => setOpen((o) => !o)}
          >
            {open ? "Close" : "Configure"}
          </button>
        )}
      </div>
      {open && configurable && <SourceConfig state={state} onPatch={onPatch} />}
    </div>
  );
}

// Refresh interval as a friendly value + unit (minutes / hours). The store still
// holds raw refreshSeconds; we convert on load and on save. Floors at 15 min / 1 hr.
type RefreshUnit = "min" | "hr";

function RefreshControl({
  seconds,
  onChange,
}: {
  seconds: number;
  onChange: (seconds: number) => void;
}) {
  // A whole number of hours shows as hours; everything else as minutes.
  const derivedUnit: RefreshUnit = seconds % 3600 === 0 && seconds >= 3600 ? "hr" : "min";
  const [unit, setUnit] = useState<RefreshUnit>(derivedUnit);

  const floor = (u: RefreshUnit) => (u === "hr" ? 1 : 15);
  // Display the true stored value (never lie about it); only the floor below
  // applies when the user actually edits/commits a new interval.
  const toValue = (s: number, u: RefreshUnit) =>
    Math.max(1, Math.round(u === "hr" ? s / 3600 : s / 60));
  const toSeconds = (v: number, u: RefreshUnit) =>
    Math.max(floor(u), Math.round(v || 0)) * (u === "hr" ? 3600 : 60);

  const [draft, setDraft] = useState(String(toValue(seconds, unit)));

  // Re-sync the draft whenever the stored value or unit changes externally.
  useEffect(() => setDraft(String(toValue(seconds, unit))), [seconds, unit]);

  function changeUnit(u: RefreshUnit) {
    setUnit(u); // keep the same real duration, re-expressed in the new unit
    onChange(toSeconds(toValue(seconds, u), u));
  }

  return (
    <label className="text-xs flex items-center gap-1" style={{ color: "var(--text-muted)" }}>
      every
      <input
        type="number"
        min={floor(unit)}
        value={draft}
        onChange={(e) => setDraft(e.target.value)}
        onBlur={() => onChange(toSeconds(Number(draft), unit))}
        className="w-14 px-1 py-0.5 text-xs"
        style={inputStyle}
      />
      <select
        value={unit}
        onChange={(e) => changeUnit(e.target.value as RefreshUnit)}
        className="text-xs px-1 py-0.5"
        style={inputStyle}
        aria-label="Refresh unit"
      >
        <option value="min">min</option>
        <option value="hr">hr</option>
      </select>
    </label>
  );
}

function SourceConfig({
  state,
  onPatch,
}: {
  state: SourceState;
  onPatch: (patch: Record<string, unknown>) => void;
}) {
  const cfg = state.source.config as Record<string, unknown>;
  if (state.source.kind === "news" || state.source.kind === "feeds") {
    const feeds = Array.isArray(cfg.feeds) ? (cfg.feeds as string[]).join("\n") : "";
    return (
      <div className="mt-3 space-y-2">
        <Field label="Feed URLs (one per line)">
          <textarea
            rows={4}
            defaultValue={feeds}
            className="w-full px-3 py-2 text-xs font-mono"
            style={inputStyle}
            onBlur={(e) =>
              onPatch({
                config: {
                  ...cfg,
                  feeds: e.target.value.split("\n").map((s) => s.trim()).filter(Boolean),
                },
              })
            }
          />
        </Field>
      </div>
    );
  }
  if (state.source.kind === "obsidian") {
    return (
      <div className="mt-3 space-y-2">
        <Field label="Vault path">
          <input
            defaultValue={String(cfg.vault ?? "")}
            className="w-full px-3 py-2 text-xs font-mono"
            style={inputStyle}
            onBlur={(e) => onPatch({ config: { ...cfg, vault: e.target.value } })}
          />
        </Field>
      </div>
    );
  }
  if (state.source.kind === "calendar") {
    return <CalendarConfigEditor cfg={cfg} onPatch={onPatch} />;
  }
  if (state.source.kind === "links") {
    const rows = Array.isArray(cfg.links)
      ? (cfg.links as Array<{ label: string; url: string }>).map((l) => `${l.label} | ${l.url}`).join("\n")
      : "";
    const parse = (v: string) =>
      v
        .split("\n")
        .map((line) => {
          const [label, url] = line.split("|").map((s) => s.trim());
          return label && url ? { label, url } : null;
        })
        .filter(Boolean);
    return (
      <div className="mt-3 space-y-2">
        <Field label="Links (one per line: label | url)">
          <textarea
            rows={5}
            defaultValue={rows}
            placeholder={"Pile | https://pile.kortum.dev\nBoo | https://boo.kortum.dev"}
            className="w-full px-3 py-2 text-xs font-mono"
            style={inputStyle}
            onBlur={(e) => onPatch({ config: { ...cfg, links: parse(e.target.value) } })}
          />
        </Field>
      </div>
    );
  }
  if (state.source.kind === "sports") {
    const leagues = Array.isArray(cfg.leagues) ? (cfg.leagues as string[]).join("\n") : "";
    const teamRows = Array.isArray(cfg.teams)
      ? (cfg.teams as Array<{ league: string; team: string }>)
          .map((t) => `${t.league} | ${t.team}`)
          .join("\n")
      : "";
    const lines = (v: string) => v.split("\n").map((s) => s.trim()).filter(Boolean);
    const parseTeams = (v: string) =>
      lines(v)
        .map((l) => {
          const [league, team] = l.split("|").map((s) => s.trim());
          return league && team ? { league, team } : null;
        })
        .filter(Boolean);
    return (
      <div className="mt-3 space-y-2">
        <Field label="Leagues — show ALL games (one ESPN path per line, e.g. basketball/nba)">
          <textarea
            rows={2}
            defaultValue={leagues}
            placeholder={"basketball/nba"}
            className="w-full px-3 py-2 text-xs font-mono"
            style={inputStyle}
            onBlur={(e) => onPatch({ config: { ...cfg, leagues: lines(e.target.value) } })}
          />
        </Field>
        <Field label="Favorite teams — added on top (one per line: league | team)">
          <textarea
            rows={4}
            defaultValue={teamRows}
            placeholder={"baseball/mlb | Cubs\nfootball/nfl | Bears\nhockey/nhl | Blackhawks"}
            className="w-full px-3 py-2 text-xs font-mono"
            style={inputStyle}
            onBlur={(e) => onPatch({ config: { ...cfg, teams: parseTeams(e.target.value) } })}
          />
        </Field>
        <p className="text-xs" style={{ color: "var(--text-faint)" }}>
          ESPN paths: basketball/nba · baseball/mlb · football/nfl · hockey/nhl · soccer/eng.1
        </p>
      </div>
    );
  }
  if (state.source.kind === "markets") {
    const symbols = Array.isArray(cfg.symbols) ? (cfg.symbols as string[]).join("\n") : "";
    const lines = (v: string) => v.split("\n").map((s) => s.trim().toUpperCase()).filter(Boolean);
    return (
      <div className="mt-3 space-y-2">
        <Field label="Symbols (one per line — crypto as PAIR, e.g. BTC-USD)">
          <textarea
            rows={5}
            defaultValue={symbols}
            placeholder={"AAPL\nNVDA\nBTC-USD\nETH-USD"}
            className="w-full px-3 py-2 text-xs font-mono"
            style={inputStyle}
            onBlur={(e) => onPatch({ config: { ...cfg, symbols: lines(e.target.value) } })}
          />
        </Field>
      </div>
    );
  }
  if (state.source.kind === "hackernews") {
    const subs = Array.isArray(cfg.subreddits) ? (cfg.subreddits as string[]).join("\n") : "";
    const lines = (v: string) => v.split("\n").map((s) => s.trim().replace(/^r\//, "")).filter(Boolean);
    return (
      <div className="mt-3 space-y-2">
        <Field label="Subreddits (optional, one per line — HN is always included)">
          <textarea
            rows={3}
            defaultValue={subs}
            placeholder={"programming\ntechnology"}
            className="w-full px-3 py-2 text-xs font-mono"
            style={inputStyle}
            onBlur={(e) => onPatch({ config: { ...cfg, subreddits: lines(e.target.value) } })}
          />
        </Field>
      </div>
    );
  }
  if (state.source.kind === "uptime") {
    const urls = Array.isArray(cfg.urls) ? (cfg.urls as string[]).join("\n") : "";
    const lines = (v: string) => v.split("\n").map((s) => s.trim()).filter(Boolean);
    return (
      <div className="mt-3 space-y-2">
        <Field label="URLs to ping (one per line)">
          <textarea
            rows={5}
            defaultValue={urls}
            placeholder={"https://humdrum.one\nhttps://screamer.humdrum.one"}
            className="w-full px-3 py-2 text-xs font-mono"
            style={inputStyle}
            onBlur={(e) => onPatch({ config: { ...cfg, urls: lines(e.target.value) } })}
          />
        </Field>
      </div>
    );
  }
  return null;
}

function CalendarConfigEditor({
  cfg,
  onPatch,
}: {
  cfg: Record<string, unknown>;
  onPatch: (patch: Record<string, unknown>) => void;
}) {
  const mode = (cfg.mode as string) ?? "local";
  const selected = Array.isArray(cfg.calendars) ? (cfg.calendars as string[]) : [];
  const urls = Array.isArray(cfg.urls) ? (cfg.urls as string[]).join("\n") : "";
  const [available, setAvailable] = useState<string[]>([]);
  const [calError, setCalError] = useState<string | null>(null);

  useEffect(() => {
    if (mode !== "local") return;
    fetch("/api/integrations/calendars")
      .then((r) => r.json())
      .then((j) => {
        setAvailable(j.calendars ?? []);
        if (j.error) setCalError(j.error);
      })
      .catch(() => setCalError("Couldn't list calendars."));
  }, [mode]);

  function toggleCal(name: string) {
    const next = selected.includes(name)
      ? selected.filter((c) => c !== name)
      : [...selected, name];
    onPatch({ config: { ...cfg, calendars: next } });
  }

  return (
    <div className="mt-3 space-y-3">
      <div className="flex gap-2">
        {(["local", "ics"] as const).map((m) => (
          <Toggle key={m} active={mode === m} onClick={() => onPatch({ config: { ...cfg, mode: m } })}>
            {m === "local" ? "Calendar.app" : "ICS URL"}
          </Toggle>
        ))}
      </div>

      {mode === "local" ? (
        <Field label="Calendars to show">
          {calError && <p className="text-xs" style={{ color: "var(--red)" }}>{calError}</p>}
          <div className="grid grid-cols-2 gap-1">
            {available.map((name) => (
              <label key={name} className="flex items-center gap-2 text-xs">
                <input
                  type="checkbox"
                  checked={selected.includes(name)}
                  onChange={() => toggleCal(name)}
                />
                <span className="truncate">{name}</span>
              </label>
            ))}
            {available.length === 0 && !calError && (
              <span className="text-xs" style={{ color: "var(--text-faint)" }}>Loading…</span>
            )}
          </div>
        </Field>
      ) : (
        <Field label="ICS URLs (one per line)">
          <textarea
            rows={3}
            defaultValue={urls}
            className="w-full px-3 py-2 text-xs font-mono"
            style={inputStyle}
            onBlur={(e) =>
              onPatch({
                config: { ...cfg, urls: e.target.value.split("\n").map((s) => s.trim()).filter(Boolean) },
              })
            }
          />
        </Field>
      )}
    </div>
  );
}

function GeoSearch({
  current,
  onPick,
}: {
  current: string | null;
  onPick: (loc: { label: string; lat: number; lon: number }) => void;
}) {
  const [q, setQ] = useState("");
  const [busy, setBusy] = useState(false);
  const [err, setErr] = useState<string | null>(null);

  async function search(e: React.FormEvent) {
    e.preventDefault();
    const name = q.trim();
    if (!name) return;
    setBusy(true);
    setErr(null);
    try {
      const r = await fetch(
        `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(name)}&count=1&language=en&format=json`,
      );
      const j = await r.json();
      const hit = j.results?.[0];
      if (!hit) {
        setErr("No match found.");
        return;
      }
      const label = [hit.name, hit.admin1, hit.country_code].filter(Boolean).join(", ");
      onPick({ label, lat: Number(hit.latitude.toFixed(4)), lon: Number(hit.longitude.toFixed(4)) });
      setQ("");
    } catch {
      setErr("Search failed.");
    } finally {
      setBusy(false);
    }
  }

  return (
    <form onSubmit={search} className="space-y-1">
      <div className="flex gap-2">
        <input
          value={q}
          onChange={(e) => setQ(e.target.value)}
          placeholder="Search a place — e.g. Napa, CA"
          className="flex-1 px-3 py-2 text-sm"
          style={inputStyle}
        />
        <button type="submit" disabled={busy} className="px-3 py-2 text-sm" style={inputStyle}>
          {busy ? "…" : "Search"}
        </button>
      </div>
      <p className="text-xs" style={{ color: err ? "var(--red)" : "var(--text-faint)" }}>
        {err ?? (current ? `Current: ${current}` : "No location set")}
      </p>
    </form>
  );
}

function Field({ label, children }: { label: string; children: React.ReactNode }) {
  return (
    <label className="block space-y-1">
      <span className="text-xs uppercase tracking-widest" style={{ color: "var(--text-muted)" }}>
        {label}
      </span>
      {children}
    </label>
  );
}

function Toggle({
  active,
  onClick,
  children,
}: {
  active: boolean;
  onClick: () => void;
  children: React.ReactNode;
}) {
  return (
    <button
      onClick={onClick}
      className="px-3 py-1.5 text-sm capitalize"
      style={{
        background: active ? "var(--accent)" : "var(--bg-elevated)",
        color: active ? "var(--text-on-accent)" : "var(--text)",
        border: "1px solid var(--border)",
        borderRadius: 6,
      }}
    >
      {children}
    </button>
  );
}
