import { useEffect, useMemo, useState } from 'react';

import { fetchChangelog, type ChangelogEntry } from '../lib/changelog';
import { SparklesIcon, SpinnerIcon } from '../components/icons';

const PAGE_SIZE = 10;

// Installed app version comes from the preload bridge (process.env.npm_-
// package_version at preload build time). Falls back to '0.0.0' outside
// Electron so the page still renders in a browser preview.
const installedVersion = window.electronAPI?.appVersion ?? '0.0.0';

export function ChangelogPage() {
  const [entries, setEntries] = useState<ChangelogEntry[] | null>(null);
  const [error, setError] = useState<string | null>(null);
  const [visible, setVisible] = useState(PAGE_SIZE);

  // Compare the installed version against the top changelog entry. The
  // server-side changelog is sorted newest-first by the release script, so
  // entries[0] is always the published latest.
  const latestVersion = entries?.[0]?.version ?? null;
  const versionStatus = useMemo<'loading' | 'current' | 'outdated' | 'ahead'>(() => {
    if (entries === null) return 'loading';
    if (!latestVersion) return 'current';
    const cmp = compareSemver(installedVersion, latestVersion);
    if (cmp === 0) return 'current';
    if (cmp < 0) return 'outdated';
    return 'ahead';
  }, [entries, latestVersion]);

  useEffect(() => {
    let cancelled = false;
    void (async () => {
      try {
        const list = await fetchChangelog();
        if (!cancelled) setEntries(list);
      } catch (err: unknown) {
        if (!cancelled) {
          setError(err instanceof Error ? err.message : 'Konnte Changelog nicht laden.');
        }
      }
    })();
    return () => {
      cancelled = true;
    };
  }, []);

  return (
    <div className="min-h-full bg-surface-3 text-fg">
      <div className="mx-auto flex max-w-3xl flex-col gap-6 px-6 py-8">
        <header className="mb-2 flex items-start gap-3">
          <div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-accent/15 text-accent">
            <SparklesIcon className="h-5 w-5" />
          </div>
          <div className="flex-1">
            <h1 className="font-display text-2xl font-semibold tracking-tight text-fg">
              Was ist neu
            </h1>
            <p className="mt-0.5 text-sm text-fg-muted">
              Alle Änderungen in dieser App, neueste zuerst.
            </p>
          </div>
          <VersionBadge
            status={versionStatus}
            installed={installedVersion}
            latest={latestVersion}
          />
        </header>

        {entries === null && !error && (
          <div className="flex items-center gap-2 rounded-xl border border-line bg-surface-2 p-6 text-sm text-fg-muted">
            <SpinnerIcon className="h-4 w-4 text-accent" />
            <span>Lade Changelog…</span>
          </div>
        )}

        {error && (
          <div className="rounded-xl border border-rose-500/30 bg-rose-500/10 p-4 text-sm text-rose-600 dark:text-rose-200">
            <p className="font-semibold">Fehler beim Laden</p>
            <p className="mt-1 text-xs opacity-90">{error}</p>
          </div>
        )}

        {entries !== null && entries.length === 0 && !error && (
          <div className="rounded-xl border border-line bg-surface-2 p-8 text-center text-sm text-fg-muted">
            Noch keine Einträge vorhanden.
          </div>
        )}

        {entries && entries.length > 0 && (
          <ol className="flex flex-col gap-4">
            {entries.slice(0, visible).map((entry) => (
              <li
                key={entry.version}
                className="rounded-2xl border border-line bg-surface-2 p-5 shadow-sm"
              >
                <div className="flex flex-wrap items-baseline justify-between gap-3">
                  <h2 className="font-display text-lg font-semibold tracking-tight text-fg">
                    v{entry.version}
                  </h2>
                  <time
                    dateTime={entry.pub_date}
                    className="text-xs tabular-nums text-fg-muted"
                  >
                    {formatDate(entry.pub_date)}
                  </time>
                </div>
                <p className="mt-3 whitespace-pre-line break-words text-sm leading-relaxed text-fg">
                  {entry.notes}
                </p>
              </li>
            ))}
            {visible < entries.length && (
              <li>
                <button
                  type="button"
                  onClick={() => setVisible((n) => n + PAGE_SIZE)}
                  className="inline-flex w-full cursor-pointer items-center justify-center rounded-lg border border-line bg-surface-2 px-3 py-2.5 text-sm font-semibold text-fg transition hover:bg-surface-3 focus:outline-none focus-visible:ring-2 focus-visible:ring-accent/40"
                >
                  Mehr laden ({entries.length - visible} verbleibend)
                </button>
              </li>
            )}
          </ol>
        )}
      </div>
    </div>
  );
}

function formatDate(iso: string): string {
  try {
    const d = new Date(iso);
    if (Number.isNaN(d.getTime())) return iso;
    return d.toLocaleDateString('de-DE', {
      year: 'numeric',
      month: 'short',
      day: 'numeric',
    });
  } catch {
    return iso;
  }
}

// Compact status chip in the header that tells the user whether their
// installed build matches the latest published version. Three visual
// tones: emerald (current), amber (outdated → update available), neutral
// (loading / unknown). The "ahead" case (dev build > released) shares the
// neutral tone since users running it always know what they're doing.
function VersionBadge({
  status,
  installed,
  latest,
}: {
  status: 'loading' | 'current' | 'outdated' | 'ahead';
  installed: string;
  latest: string | null;
}) {
  if (status === 'loading') {
    return (
      <span className="inline-flex shrink-0 items-center gap-1.5 rounded-full border border-line bg-surface-2 px-2.5 py-1 text-[11px] font-medium tabular-nums text-fg-muted">
        v{installed}
      </span>
    );
  }
  if (status === 'current') {
    return (
      <span
        title="Du läufst auf der neuesten Version."
        className="inline-flex shrink-0 items-center gap-1.5 rounded-full border border-emerald-500/40 bg-emerald-500/10 px-2.5 py-1 text-[11px] font-semibold tabular-nums text-emerald-700 dark:text-emerald-300"
      >
        <span className="h-1.5 w-1.5 rounded-full bg-emerald-500" />
        v{installed} · aktuell
      </span>
    );
  }
  if (status === 'outdated' && latest) {
    return (
      <span
        title={`Update verfügbar — neueste Version: v${latest}.`}
        className="inline-flex shrink-0 flex-col items-end gap-0.5 rounded-md border border-amber-400/50 bg-amber-400/10 px-2.5 py-1 text-[11px] font-semibold tabular-nums text-amber-800 dark:text-amber-200"
      >
        <span>v{installed} · Update verfügbar</span>
        <span className="text-[10px] font-normal opacity-80">neueste: v{latest}</span>
      </span>
    );
  }
  return (
    <span
      title="Du läufst auf einer neueren Version als veröffentlicht (z.B. Dev-Build)."
      className="inline-flex shrink-0 items-center gap-1.5 rounded-full border border-line bg-surface-2 px-2.5 py-1 text-[11px] font-medium tabular-nums text-fg-muted"
    >
      v{installed}
    </span>
  );
}

// Lightweight semver comparator: parses major.minor.patch as ints and
// compares numerically. Returns negative if a < b, zero if equal, positive
// if a > b. Handles malformed inputs by treating non-numeric segments as
// 0 so a typo doesn't flag a perfectly current install as outdated.
function compareSemver(a: string, b: string): number {
  const parse = (s: string): [number, number, number] => {
    const parts = s.split('.').map((p) => {
      const n = parseInt(p, 10);
      return Number.isFinite(n) ? n : 0;
    });
    return [parts[0] ?? 0, parts[1] ?? 0, parts[2] ?? 0];
  };
  const [aMaj, aMin, aPat] = parse(a);
  const [bMaj, bMin, bPat] = parse(b);
  if (aMaj !== bMaj) return aMaj - bMaj;
  if (aMin !== bMin) return aMin - bMin;
  return aPat - bPat;
}
