import { useState, useCallback, useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import type { AuthUser } from '../../hooks/useAuth';
import type { SavedSearch } from '../../hooks/useSavedSearches';
import {
  apiUrl,
  authHeaders,
  assertOk,
  shortenUrl,
  prewarmScreenshot,
  paramsWithLanguage,
} from '../../lib/api';
import { copyToClipboard } from '../../lib/clipboard';
import { formatRelativeTime, formatNumber } from '../../lib/format';
import { summarizeParams } from '../../lib/url-state';
import { SpinnerIcon } from '../ui/icons/SpinnerIcon';
import { CheckIcon } from '../ui/icons/CheckIcon';
import { ClipboardIcon } from '../ui/icons/ClipboardIcon';
import { BookmarkIcon } from '../ui/icons/BookmarkIcon';
import { TrashIcon } from '../ui/icons/TrashIcon';
import { CloseIcon } from '../ui/icons/CloseIcon';
import { useLicense } from '../../hooks/useLicense';

function PageLayout({ children }: { children: React.ReactNode }) {
  return (
    <div className="flex-1 overflow-hidden bg-warm-50 dark:bg-warm-900 flex flex-col">
      <div className="flex-1 overflow-y-auto">
        <div className="max-w-5xl mx-auto px-6 py-6">{children}</div>
      </div>
    </div>
  );
}

function DeleteDialog({
  title,
  message,
  onCancel,
  onConfirm,
}: {
  title: string;
  message: string;
  onCancel: () => void;
  onConfirm: () => void;
}) {
  const { t } = useTranslation();
  return (
    <div className="fixed inset-0 z-50 flex items-center justify-center" onClick={onCancel}>
      <div className="absolute inset-0 bg-black/50 dark:bg-black/70" />
      <div
        className="relative w-full max-w-sm mx-4 bg-white dark:bg-warm-800 rounded-lg shadow-xl border border-warm-200 dark:border-warm-700"
        onClick={(e) => e.stopPropagation()}
      >
        <div className="flex items-center justify-between px-5 pt-5 pb-3">
          <h2 className="text-lg font-semibold text-navy-950 dark:text-white">{title}</h2>
          <button
            onClick={onCancel}
            className="text-warm-400 hover:text-warm-700 dark:text-warm-400 dark:hover:text-warm-200"
          >
            <CloseIcon className="w-5 h-5" />
          </button>
        </div>
        <p className="px-5 pb-4 text-sm text-warm-700 dark:text-warm-300">{message}</p>
        <div className="flex gap-3 justify-end px-5 pb-5">
          <button
            onClick={onCancel}
            className="px-4 py-2 text-sm rounded border border-warm-200 dark:border-warm-700 text-warm-700 dark:text-warm-300 hover:bg-warm-50 dark:hover:bg-warm-700"
          >
            {t('common.cancel')}
          </button>
          <button
            onClick={onConfirm}
            className="px-4 py-2 text-sm rounded bg-red-600 text-white font-medium hover:bg-red-700"
          >
            {t('common.delete')}
          </button>
        </div>
      </div>
    </div>
  );
}

function NotesInput({ value, onSave }: { value: string; onSave: (notes: string) => void }) {
  const { t } = useTranslation();
  const [text, setText] = useState(value);
  const textareaRef = useRef<HTMLTextAreaElement>(null);
  const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);

  // Sync from parent when value changes externally
  useEffect(() => {
    setText(value);
  }, [value]);

  const autoResize = useCallback(() => {
    const el = textareaRef.current;
    if (!el) return;
    el.style.height = 'auto';
    el.style.height = `${el.scrollHeight}px`;
  }, []);

  // Resize on mount and when text changes
  useEffect(() => {
    autoResize();
  }, [text, autoResize]);

  const handleChange = useCallback(
    (e: React.ChangeEvent<HTMLTextAreaElement>) => {
      const newText = e.target.value;
      setText(newText);
      if (timerRef.current) clearTimeout(timerRef.current);
      timerRef.current = setTimeout(() => onSave(newText), 600);
    },
    [onSave]
  );

  // Save immediately on blur
  const handleBlur = useCallback(() => {
    if (timerRef.current) clearTimeout(timerRef.current);
    if (text !== value) onSave(text);
  }, [text, value, onSave]);

  return (
    <textarea
      ref={textareaRef}
      value={text}
      onChange={handleChange}
      onBlur={handleBlur}
      placeholder={t('savedPage.notesPlaceholder')}
      rows={1}
      className="w-full resize-none overflow-hidden rounded border border-warm-200 dark:border-warm-700 bg-warm-50 dark:bg-warm-900 px-3 py-1.5 text-sm text-warm-700 dark:text-warm-300 placeholder-warm-400 dark:placeholder-warm-500 focus:outline-none focus:ring-1 focus:ring-teal-400"
    />
  );
}

function EditableName({ value, onSave }: { value: string; onSave: (name: string) => void }) {
  const { t } = useTranslation();
  const [editing, setEditing] = useState(false);
  const [text, setText] = useState(value);
  const inputRef = useRef<HTMLInputElement>(null);

  useEffect(() => {
    setText(value);
  }, [value]);

  useEffect(() => {
    if (editing) {
      inputRef.current?.focus();
      inputRef.current?.select();
    }
  }, [editing]);

  const commit = () => {
    setEditing(false);
    const trimmed = text.trim();
    if (trimmed && trimmed !== value) onSave(trimmed);
    else setText(value);
  };

  if (editing) {
    return (
      <input
        ref={inputRef}
        value={text}
        onChange={(e) => setText(e.target.value)}
        onKeyDown={(e) => {
          if (e.key === 'Enter') commit();
          if (e.key === 'Escape') {
            setText(value);
            setEditing(false);
          }
        }}
        onBlur={commit}
        className="w-full font-medium text-navy-950 dark:text-warm-100 bg-warm-50 dark:bg-warm-900 border border-warm-200 dark:border-warm-700 rounded px-1.5 py-0.5 text-sm focus:outline-none focus:ring-1 focus:ring-teal-400"
      />
    );
  }

  return (
    <h3
      onClick={() => setEditing(true)}
      className="font-medium text-navy-950 dark:text-warm-100 truncate cursor-pointer hover:text-teal-600 dark:hover:text-teal-400 border-b border-dotted border-transparent hover:border-warm-400 dark:hover:border-warm-500"
      title={t('savedPage.clickToRename')}
    >
      {value}
    </h3>
  );
}

function SavedSearchesTab({
  searches,
  loading,
  onDelete,
  onUpdateNotes,
  onUpdateName,
  onOpen,
}: {
  searches: SavedSearch[];
  loading: boolean;
  onDelete: (id: string) => Promise<void>;
  onUpdateNotes: (id: string, notes: string) => void;
  onUpdateName: (id: string, name: string) => void;
  onOpen: (id: string, name: string, params: string) => void;
}) {
  const { t, i18n } = useTranslation();
  const [deleteConfirmId, setDeleteConfirmId] = useState<string | null>(null);
  const [copiedId, setCopiedId] = useState<string | null>(null);
  const [sharingId, setSharingId] = useState<string | null>(null);

  const handleDeleteConfirm = useCallback(async () => {
    if (!deleteConfirmId) return;
    await onDelete(deleteConfirmId);
    setDeleteConfirmId(null);
  }, [deleteConfirmId, onDelete]);

  const doCopy = useCallback((text: string, id: string) => {
    copyToClipboard(text, () => {
      setCopiedId(id);
      setTimeout(() => setCopiedId(null), 2000);
    });
  }, []);

  const handleShare = useCallback(
    async (params: string, id: string) => {
      prewarmScreenshot(params, i18n.language);
      setSharingId(id);
      try {
        const shortUrl = await shortenUrl(params, i18n.language);
        doCopy(shortUrl, id);
      } catch {
        doCopy(
          `${window.location.origin}/dashboard?${paramsWithLanguage(params, i18n.language)}`,
          id
        );
      } finally {
        setSharingId(null);
      }
    },
    [doCopy, i18n.language]
  );

  if (loading) {
    return (
      <div className="flex items-center justify-center py-20">
        <SpinnerIcon className="w-8 h-8 text-teal-600 dark:text-teal-400 animate-spin" />
      </div>
    );
  }

  if (searches.length === 0) {
    return (
      <div className="flex flex-col items-center justify-center py-20 text-center">
        <BookmarkIcon className="w-12 h-12 text-warm-300 dark:text-warm-600 mb-4" />
        <p className="text-lg font-medium text-warm-600 dark:text-warm-400 mb-1">
          {t('savedPage.noSavedSearches')}
        </p>
        <p className="text-sm text-warm-500 dark:text-warm-500">
          {t('savedPage.noSavedSearchesDesc')}
        </p>
      </div>
    );
  }

  return (
    <>
      <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
        {searches.map((search) => (
          <div
            key={search.id}
            className="flex flex-col bg-white dark:bg-warm-800 border border-warm-200 dark:border-warm-700 rounded-lg overflow-hidden"
          >
            {search.screenshotUrl ? (
              <img
                src={search.screenshotUrl}
                alt={search.name}
                className="w-full h-36 object-cover"
              />
            ) : (
              <div className="w-full h-36 bg-gradient-to-br from-teal-600/20 to-navy-900/30 dark:from-teal-400/10 dark:to-navy-900/40 flex items-center justify-center">
                <BookmarkIcon className="w-10 h-10 text-warm-300 dark:text-warm-600" />
              </div>
            )}

            <div className="p-4 flex flex-col flex-1">
              <div className="mb-1">
                <EditableName
                  value={search.name}
                  onSave={(name) => onUpdateName(search.id, name)}
                />
              </div>
              <p className="text-xs text-warm-500 dark:text-warm-400 mb-1">
                {formatRelativeTime(search.created)}
              </p>
              <p className="text-xs text-warm-500 dark:text-warm-400 mb-2">
                {summarizeParams(search.params)}
              </p>

              <div className="mb-3 flex-1">
                <NotesInput
                  value={search.notes}
                  onSave={(notes) => onUpdateNotes(search.id, notes)}
                />
              </div>

              <div className="flex gap-2 mt-auto">
                <button
                  onClick={() => onOpen(search.id, search.name, search.params)}
                  className="flex-1 px-3 py-1.5 text-sm font-medium rounded bg-teal-600 text-white hover:bg-teal-700"
                >
                  {t('common.open')}
                </button>
                <button
                  onClick={() => handleShare(search.params, search.id)}
                  disabled={sharingId === search.id}
                  className="px-3 py-1.5 text-sm rounded border border-warm-200 dark:border-warm-700 text-warm-500 dark:text-warm-400 hover:text-warm-700 dark:hover:text-warm-300 disabled:opacity-50 disabled:cursor-wait"
                >
                  {sharingId === search.id ? (
                    <SpinnerIcon className="w-4 h-4 animate-spin" />
                  ) : copiedId === search.id ? (
                    t('common.copied')
                  ) : (
                    t('common.share')
                  )}
                </button>
                <button
                  onClick={() => setDeleteConfirmId(search.id)}
                  className="px-3 py-1.5 text-sm rounded border border-warm-200 dark:border-warm-700 text-warm-500 dark:text-warm-400 hover:text-warm-700 dark:hover:text-warm-300"
                  title={t('common.delete')}
                >
                  <TrashIcon className="w-4 h-4" />
                </button>
              </div>
            </div>
          </div>
        ))}
      </div>

      {deleteConfirmId && (
        <DeleteDialog
          title={t('savedPage.deleteSearch')}
          message={t('savedPage.deleteSearchConfirm')}
          onCancel={() => setDeleteConfirmId(null)}
          onConfirm={handleDeleteConfirm}
        />
      )}
    </>
  );
}

export function SavedPage({
  searches,
  searchesLoading,
  onDeleteSearch,
  onUpdateSearchNotes,
  onUpdateSearchName,
  onOpenSearch,
}: {
  searches: SavedSearch[];
  searchesLoading: boolean;
  onDeleteSearch: (id: string) => Promise<void>;
  onUpdateSearchNotes: (id: string, notes: string) => void;
  onUpdateSearchName: (id: string, name: string) => void;
  onOpenSearch: (id: string, name: string, params: string) => void;
}) {
  const { t } = useTranslation();
  const [activeTab, setActiveTab] = useState<'searches' | 'shared-links'>(
    window.location.hash === '#shared-links' ? 'shared-links' : 'searches'
  );
  const [shareLinks, setShareLinks] = useState<ShareLinkListItem[]>([]);
  const [shareLinksLoading, setShareLinksLoading] = useState(false);
  const [shareLinksError, setShareLinksError] = useState<string | null>(null);

  useEffect(() => {
    let cancelled = false;
    setShareLinksLoading(true);
    setShareLinksError(null);

    fetch(apiUrl('share-links'), authHeaders())
      .then((res) => {
        assertOk(res, 'Fetch share links');
        return res.json();
      })
      .then((data: { links: ShareLinkListItem[] }) => {
        if (!cancelled) setShareLinks(data.links);
      })
      .catch((err) => {
        if (!cancelled) {
          setShareLinksError(err instanceof Error ? err.message : 'Failed to fetch share links');
        }
      })
      .finally(() => {
        if (!cancelled) setShareLinksLoading(false);
      });

    return () => {
      cancelled = true;
    };
  }, []);

  const tabClass = (tab: string) =>
    `px-4 py-2 text-sm font-medium border-b-2 transition-colors ${
      activeTab === tab
        ? 'border-teal-600 dark:border-teal-400 text-teal-600 dark:text-teal-400'
        : 'border-transparent text-warm-500 dark:text-warm-400 hover:text-warm-700 dark:hover:text-warm-300'
    }`;

  return (
    <PageLayout>
      <div className="flex border-b border-warm-200 dark:border-warm-700 mb-6">
        <button className={tabClass('searches')} onClick={() => setActiveTab('searches')}>
          {t('savedPage.searches')}
          {searches.length > 0 && (
            <span className="ml-1.5 text-xs bg-warm-100 dark:bg-warm-700 text-warm-600 dark:text-warm-300 rounded-full px-1.5 py-0.5">
              {searches.length}
            </span>
          )}
        </button>
        <button className={tabClass('shared-links')} onClick={() => setActiveTab('shared-links')}>
          {t('accountPage.shareLinksTitle')}
          {shareLinks.length > 0 && (
            <span className="ml-1.5 text-xs bg-warm-100 dark:bg-warm-700 text-warm-600 dark:text-warm-300 rounded-full px-1.5 py-0.5">
              {shareLinks.length}
            </span>
          )}
        </button>
      </div>

      {activeTab === 'searches' ? (
        <SavedSearchesTab
          searches={searches}
          loading={searchesLoading}
          onDelete={onDeleteSearch}
          onUpdateNotes={onUpdateSearchNotes}
          onUpdateName={onUpdateSearchName}
          onOpen={onOpenSearch}
        />
      ) : (
        <ShareLinksSection
          links={shareLinks}
          loading={shareLinksLoading}
          error={shareLinksError}
          showTitle={false}
        />
      )}
    </PageLayout>
  );
}

interface InviteListItem {
  code: string;
  url: string;
  invite_type: string;
  used: boolean;
  created: string;
}

interface ShareLinkListItem {
  code: string;
  url: string;
  og_image_url: string;
  params: string;
  click_count: number;
  created: string;
}

function latestPendingInviteUrls(invites: InviteListItem[]): Record<string, string> {
  const latestByType: Record<string, { url: string; createdMs: number }> = {};

  for (const invite of invites) {
    if (invite.used || !invite.url) continue;

    const createdMs = Date.parse(invite.created) || 0;
    const existing = latestByType[invite.invite_type];
    if (!existing || createdMs > existing.createdMs) {
      latestByType[invite.invite_type] = { url: invite.url, createdMs };
    }
  }

  return Object.fromEntries(
    Object.entries(latestByType).map(([type, invite]) => [type, invite.url])
  );
}

function InviteTable({
  invites,
  loading,
  title,
}: {
  invites: InviteListItem[];
  loading: boolean;
  title: string;
}) {
  const { t } = useTranslation();
  const [copiedCode, setCopiedCode] = useState<string | null>(null);

  const handleCopy = (url: string, code: string) => {
    copyToClipboard(url, () => {
      setCopiedCode(code);
      setTimeout(() => setCopiedCode(null), 2000);
    });
  };

  return (
    <div className="bg-white dark:bg-warm-800 rounded-xl border border-warm-200 dark:border-warm-700 overflow-hidden">
      <div className="px-5 py-3 border-b border-warm-200 dark:border-warm-700">
        <h3 className="text-sm font-medium text-navy-950 dark:text-warm-100">{title}</h3>
      </div>
      {loading ? (
        <div className="flex items-center justify-center py-8">
          <SpinnerIcon className="w-5 h-5 text-teal-600 dark:text-teal-400 animate-spin" />
        </div>
      ) : invites.length === 0 ? (
        <p className="px-5 py-6 text-sm text-warm-500 dark:text-warm-400 text-center">
          {t('invitesPage.noInvitesYet')}
        </p>
      ) : (
        <table className="w-full table-fixed text-sm">
          <thead>
            <tr className="border-b border-warm-200 dark:border-warm-700 text-left">
              <th className="px-5 py-2 text-warm-500 dark:text-warm-400 font-medium">
                {t('invitesPage.link')}
              </th>
              <th className="px-5 py-2 text-warm-500 dark:text-warm-400 font-medium w-24">
                {t('invitesPage.status')}
              </th>
              <th className="px-5 py-2 text-warm-500 dark:text-warm-400 font-medium w-24">
                {t('invitesPage.created')}
              </th>
            </tr>
          </thead>
          <tbody className="divide-y divide-warm-100 dark:divide-warm-800">
            {invites.map((inv) => (
              <tr key={inv.code}>
                <td className="px-5 py-2.5">
                  <div className="flex items-center gap-2 min-w-0">
                    <span
                      className="text-navy-950 dark:text-warm-200 font-mono text-xs truncate min-w-0"
                      style={{ direction: 'rtl', textAlign: 'left' }}
                    >
                      {inv.url}
                    </span>
                    <button
                      onClick={() => handleCopy(inv.url, inv.code)}
                      className="shrink-0 text-warm-400 hover:text-warm-700 dark:hover:text-warm-300"
                      title={t('invitesPage.copyInviteLink')}
                    >
                      {copiedCode === inv.code ? (
                        <CheckIcon className="w-4 h-4 text-teal-600 dark:text-teal-400" />
                      ) : (
                        <ClipboardIcon className="w-4 h-4" />
                      )}
                    </button>
                  </div>
                </td>
                <td className="px-5 py-2.5">
                  <span
                    className={`inline-block text-xs font-medium px-2 py-0.5 rounded-full ${
                      inv.used
                        ? 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400'
                        : 'bg-warm-100 text-warm-600 dark:bg-warm-700 dark:text-warm-300'
                    }`}
                  >
                    {inv.used ? t('invitesPage.redeemed') : t('invitesPage.pending')}
                  </span>
                </td>
                <td className="px-5 py-2.5 text-warm-500 dark:text-warm-400 text-xs">
                  {formatRelativeTime(inv.created)}
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      )}
    </div>
  );
}

function ShareLinksSection({
  links,
  loading,
  error,
  showTitle = true,
}: {
  links: ShareLinkListItem[];
  loading: boolean;
  error: string | null;
  showTitle?: boolean;
}) {
  const { t } = useTranslation();
  const [copiedCode, setCopiedCode] = useState<string | null>(null);

  const handleCopy = (url: string, code: string) => {
    copyToClipboard(url, () => {
      setCopiedCode(code);
      setTimeout(() => setCopiedCode(null), 2000);
    });
  };

  return (
    <section className="space-y-3">
      {showTitle && (
        <h2 className="text-base font-semibold text-navy-950 dark:text-warm-100">
          {t('accountPage.shareLinksTitle')}
        </h2>
      )}
      <div className="bg-white dark:bg-warm-800 rounded-xl border border-warm-200 dark:border-warm-700 overflow-hidden">
        {loading ? (
          <div className="flex items-center justify-center py-8">
            <SpinnerIcon className="w-5 h-5 text-teal-600 dark:text-teal-400 animate-spin" />
          </div>
        ) : error ? (
          <p className="px-5 py-6 text-sm text-red-600 dark:text-red-400 text-center">{error}</p>
        ) : links.length === 0 ? (
          <p className="px-5 py-6 text-sm text-warm-500 dark:text-warm-400 text-center">
            {t('accountPage.noShareLinksYet')}
          </p>
        ) : (
          <div className="divide-y divide-warm-100 dark:divide-warm-700">
            {links.map((link) => (
              <div key={link.code} className="px-5 py-4">
                <div className="flex flex-col gap-3 sm:flex-row sm:items-start">
                  <div className="aspect-[1200/630] w-full shrink-0 overflow-hidden rounded-lg border border-warm-200 bg-warm-100 dark:border-warm-700 dark:bg-warm-900 sm:w-40">
                    <img
                      src={link.og_image_url}
                      alt=""
                      loading="lazy"
                      decoding="async"
                      className="h-full w-full object-cover"
                    />
                  </div>
                  <div className="flex min-w-0 flex-1 items-start justify-between gap-4">
                    <div className="min-w-0">
                      <div className="flex items-center gap-2 min-w-0">
                        <span
                          className="text-navy-950 dark:text-warm-200 font-mono text-xs truncate min-w-0"
                          style={{ direction: 'rtl', textAlign: 'left' }}
                        >
                          {link.url}
                        </span>
                        <button
                          onClick={() => handleCopy(link.url, link.code)}
                          className="shrink-0 text-warm-400 hover:text-warm-700 dark:hover:text-warm-300"
                          title={t('accountPage.copyShareLink')}
                        >
                          {copiedCode === link.code ? (
                            <CheckIcon className="w-4 h-4 text-teal-600 dark:text-teal-400" />
                          ) : (
                            <ClipboardIcon className="w-4 h-4" />
                          )}
                        </button>
                      </div>
                      <p className="mt-1 text-xs text-warm-500 dark:text-warm-400">
                        {summarizeParams(link.params)}
                      </p>
                      <p className="mt-1 text-xs text-warm-400 dark:text-warm-500">
                        {formatRelativeTime(link.created)}
                      </p>
                    </div>
                    <div className="shrink-0 text-right">
                      <p className="text-lg font-semibold text-navy-950 dark:text-warm-100">
                        {formatNumber(link.click_count)}
                      </p>
                      <p className="text-xs text-warm-500 dark:text-warm-400">
                        {t('accountPage.clicksLabel')}
                      </p>
                    </div>
                  </div>
                </div>
              </div>
            ))}
          </div>
        )}
      </div>
    </section>
  );
}

function InviteSection({ user }: { user: AuthUser }) {
  const { t } = useTranslation();
  const [creatingInvite, setCreatingInvite] = useState<Record<string, boolean>>({});
  const [inviteUrl, setInviteUrl] = useState<Record<string, string>>({});
  const [inviteError, setInviteError] = useState<Record<string, string>>({});
  const [inviteCopied, setInviteCopied] = useState<Record<string, boolean>>({});

  const [inviteHistory, setInviteHistory] = useState<InviteListItem[]>([]);
  const [inviteHistoryLoading, setInviteHistoryLoading] = useState(false);

  const isLicensed = user.subscription === 'licensed' || user.isAdmin;

  const fetchInviteHistory = useCallback(async () => {
    setInviteHistoryLoading(true);
    try {
      const res = await fetch(apiUrl('invites'), authHeaders());
      assertOk(res, 'Fetch invites');
      const data = await res.json();
      const invites: InviteListItem[] = Array.isArray(data.invites) ? data.invites : [];
      setInviteHistory(invites);
      const pendingInviteUrls = latestPendingInviteUrls(invites);
      setInviteUrl((prev) => {
        const next = { ...prev };
        for (const [type, url] of Object.entries(pendingInviteUrls)) {
          if (!next[type]) next[type] = url;
        }
        return next;
      });
    } catch {
      // Silent — non-critical
    } finally {
      setInviteHistoryLoading(false);
    }
  }, []);

  useEffect(() => {
    if (isLicensed) fetchInviteHistory();
  }, [isLicensed, fetchInviteHistory]);

  const handleCreateInvite = async (type: string) => {
    setCreatingInvite((prev) => ({ ...prev, [type]: true }));
    setInviteError((prev) => {
      const next = { ...prev };
      delete next[type];
      return next;
    });
    setInviteUrl((prev) => {
      const next = { ...prev };
      delete next[type];
      return next;
    });
    setInviteCopied((prev) => ({ ...prev, [type]: false }));
    try {
      const res = await fetch(apiUrl('invites'), {
        method: 'POST',
        ...authHeaders({
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ invite_type: type }),
        }),
      });
      assertOk(res, 'Create invite');
      const data = await res.json();
      setInviteUrl((prev) => ({ ...prev, [type]: data.url }));
      fetchInviteHistory();
    } catch (err) {
      const msg = err instanceof Error ? err.message : 'Failed to create invite';
      setInviteError((prev) => ({ ...prev, [type]: msg }));
    } finally {
      setCreatingInvite((prev) => ({ ...prev, [type]: false }));
    }
  };

  const handleCopyInvite = (type: string) => {
    const url = inviteUrl[type];
    if (!url) return;
    copyToClipboard(url, () => {
      setInviteCopied((prev) => ({ ...prev, [type]: true }));
      setTimeout(() => setInviteCopied((prev) => ({ ...prev, [type]: false })), 2000);
    });
  };

  if (!isLicensed) {
    return (
      <div className="bg-white dark:bg-warm-800 rounded-xl border border-warm-200 dark:border-warm-700 p-6 text-center">
        <p className="text-warm-600 dark:text-warm-300">{t('invitesPage.inviteLinksLicensed')}</p>
      </div>
    );
  }

  const adminInvites = inviteHistory.filter((i) => i.invite_type === 'admin');
  const referralInvites = inviteHistory.filter((i) => i.invite_type === 'referral');

  return (
    <div className="space-y-6">
      <div className="bg-white dark:bg-warm-800 rounded-xl border border-warm-200 dark:border-warm-700 divide-y divide-warm-200 dark:divide-warm-700">
        {(user.isAdmin ? ['admin', 'referral'] : ['referral']).map((type) => (
          <div key={type} className="px-5 py-4">
            <p className="text-sm text-warm-500 dark:text-warm-400 mb-3">
              {type === 'admin'
                ? t('invitesPage.inviteAdminLabel')
                : t('invitesPage.inviteReferralLabel')}
            </p>
            {inviteUrl[type] ? (
              <div className="flex items-center gap-2">
                <input
                  type="text"
                  readOnly
                  value={inviteUrl[type]}
                  className="flex-1 px-3 py-2 rounded-lg border border-warm-200 dark:border-warm-700 bg-warm-50 dark:bg-warm-900 text-navy-950 dark:text-warm-200 text-sm"
                />
                <button
                  onClick={() => handleCopyInvite(type)}
                  className="px-3 py-2 rounded-lg bg-teal-600 hover:bg-teal-700 text-white text-sm font-medium flex items-center gap-1.5"
                >
                  {inviteCopied[type] ? (
                    <CheckIcon className="w-4 h-4" />
                  ) : (
                    <ClipboardIcon className="w-4 h-4" />
                  )}
                  {inviteCopied[type] ? t('common.copied') : t('common.copy')}
                </button>
              </div>
            ) : (
              <button
                onClick={() => handleCreateInvite(type)}
                disabled={!!creatingInvite[type]}
                className="px-4 py-2 rounded-lg bg-teal-600 hover:bg-teal-700 text-white text-sm font-medium disabled:opacity-50 disabled:cursor-wait flex items-center gap-2"
              >
                {creatingInvite[type] && <SpinnerIcon className="w-4 h-4 animate-spin" />}
                {type === 'admin'
                  ? t('invitesPage.generateFreeInvite')
                  : t('invitesPage.generateReferralLink')}
              </button>
            )}
            {inviteError[type] && (
              <p className="mt-2 text-sm text-red-600 dark:text-red-400">{inviteError[type]}</p>
            )}
          </div>
        ))}
      </div>

      {user.isAdmin && (
        <>
          <InviteTable
            invites={adminInvites}
            loading={inviteHistoryLoading}
            title={t('invitesPage.adminInvitesTitle')}
          />
          <InviteTable
            invites={referralInvites}
            loading={inviteHistoryLoading}
            title={t('invitesPage.referralInvitesTitle')}
          />
        </>
      )}
      {!user.isAdmin && referralInvites.length > 0 && (
        <InviteTable
          invites={referralInvites}
          loading={inviteHistoryLoading}
          title={t('invitesPage.yourInviteLinks')}
        />
      )}
    </div>
  );
}

export default function AccountPage({
  user,
  onRefreshAuth,
  scrollTarget,
}: {
  user: AuthUser;
  onRefreshAuth: () => Promise<void>;
  scrollTarget?: string;
}) {
  const { t } = useTranslation();
  const [newsletterSaving, setNewsletterSaving] = useState(false);
  const [newsletterError, setNewsletterError] = useState<string | null>(null);
  const { startCheckout, checkingOut, error: checkoutError } = useLicense();
  const isLicensed = user.subscription === 'licensed' || user.isAdmin;

  const badgeColor =
    user.subscription === 'licensed'
      ? 'bg-teal-100 text-teal-700 dark:bg-teal-900/30 dark:text-teal-400'
      : 'bg-warm-100 text-warm-600 dark:bg-warm-700 dark:text-warm-300';

  useEffect(() => {
    if (scrollTarget !== 'invites') return;
    const frame = window.requestAnimationFrame(() => {
      document.getElementById('invites')?.scrollIntoView({ block: 'start', behavior: 'smooth' });
    });
    return () => window.cancelAnimationFrame(frame);
  }, [scrollTarget]);

  return (
    <PageLayout>
      <div className="max-w-lg mx-auto space-y-6">
        <div className="bg-white dark:bg-warm-800 rounded-xl border border-warm-200 dark:border-warm-700 divide-y divide-warm-200 dark:divide-warm-700">
          {/* Email */}
          <div className="px-5 py-4 flex items-center justify-between">
            <div>
              <p className="text-sm text-warm-500 dark:text-warm-400">
                {t('accountPage.emailLabel')}
              </p>
              <p className="text-navy-950 dark:text-warm-100 font-medium">{user.email}</p>
            </div>
          </div>

          {/* Subscription */}
          <div className="px-5 py-4 flex items-center justify-between">
            <div>
              <p className="text-sm text-warm-500 dark:text-warm-400">
                {t('accountPage.subscriptionLabel')}
              </p>
              <span
                className={`inline-block text-sm font-medium px-2.5 py-0.5 rounded-full mt-1 ${badgeColor}`}
              >
                {user.subscription === 'licensed' ? t('userMenu.fullAccess') : t('userMenu.demo')}
              </span>
            </div>
            {!isLicensed && (
              <div className="flex flex-col items-end">
                <button
                  onClick={() => startCheckout()}
                  disabled={checkingOut}
                  className="px-4 py-1.5 text-sm font-medium rounded-lg bg-teal-600 text-white hover:bg-teal-700 disabled:opacity-50"
                >
                  {checkingOut ? t('accountPage.redirecting') : t('accountPage.upgrade')}
                </button>
                {checkoutError && (
                  <p className="mt-1 text-xs text-red-600 dark:text-red-400">{checkoutError}</p>
                )}
              </div>
            )}
          </div>

          {/* Newsletter */}
          <div className="px-5 py-4">
            <label className="flex items-center gap-3 cursor-pointer">
              <input
                type="checkbox"
                checked={user.newsletter}
                disabled={newsletterSaving}
                onChange={async (e) => {
                  const checked = e.target.checked;
                  setNewsletterSaving(true);
                  setNewsletterError(null);
                  try {
                    const res = await fetch(apiUrl('newsletter'), {
                      method: 'PATCH',
                      ...authHeaders({
                        headers: { 'Content-Type': 'application/json' },
                        body: JSON.stringify({ newsletter: checked }),
                      }),
                    });
                    assertOk(res, 'Update newsletter');
                    await onRefreshAuth();
                  } catch (err) {
                    const msg = err instanceof Error ? err.message : 'Failed to update newsletter';
                    setNewsletterError(msg);
                  } finally {
                    setNewsletterSaving(false);
                  }
                }}
                className="w-4 h-4 accent-teal-600 rounded"
              />
              <span className="text-navy-950 dark:text-warm-100 text-sm">
                {t('accountPage.receiveNewsletter')}
              </span>
              {newsletterSaving && <SpinnerIcon className="w-4 h-4 animate-spin text-warm-400" />}
            </label>
            {newsletterError && (
              <p className="mt-2 text-sm text-red-600 dark:text-red-400">{newsletterError}</p>
            )}
          </div>
        </div>

        <section id="invites" className="scroll-mt-4 space-y-3">
          <h2 className="text-base font-semibold text-navy-950 dark:text-warm-100">
            {t('header.inviteFriends')}
          </h2>
          <InviteSection user={user} />
        </section>

        {/* Support */}
        <div className="bg-white dark:bg-warm-800 rounded-xl border border-warm-200 dark:border-warm-700 p-6 text-center">
          <p className="text-warm-600 dark:text-warm-300 mb-2">{t('accountPage.needHelp')}</p>
          <a
            href="mailto:support@perfect-postcode.co.uk"
            className="text-teal-600 dark:text-teal-400 hover:text-teal-800 dark:hover:text-teal-300 font-medium text-lg"
          >
            support@perfect-postcode.co.uk
          </a>
          <p className="text-warm-400 dark:text-warm-500 text-sm mt-2">
            {t('accountPage.responseTime')}
          </p>
        </div>
      </div>
    </PageLayout>
  );
}
