import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
import { ArrowLeft, Check, ChevronRight, Cloud, CloudDownload, Crop, Download, Home, Images, Languages, Link2Off, LockKeyhole, Plus, RotateCcw, RotateCw, Save, Server, Settings, Shuffle, Smartphone, Sparkles, Trash2, Wifi } from "lucide-react";
import { applyWallpaper, connectImmich, deleteImages, disconnectImmich, getGallery, getImageIds, getImmichAlbums, getImmichAssets, getImmichConnection, getImmichImportProgress, getState, importImmichAssets, nextWallpaper, selectImages, setImageCrop, setIntervalMinutes, setSetting, type GalleryImage, type ImmichAlbum, type ImmichAsset, type ImmichConnection, type ImmichImportProgress, type WallpaperState } from "./native";
import { initialLanguage, languageNames, languages, translations, type Language } from "./i18n-local";
import { immichTranslations } from "./immich-i18n";

const initial: WallpaperState = { imageCount: 0, enabled: false, intervalMinutes: 0, shuffle: true, lockScreenOnly: true, allowMobileData: false, prefetchImmich: true, currentIndex: 0, currentId: null, imageIds: [], imageUrls: [] };

function Switch({ checked, onChange, label }: { checked: boolean; onChange: (value: boolean) => void; label: string }) {
  return <button className={`switch ${checked ? "on" : ""}`} role="switch" aria-checked={checked} aria-label={label} onClick={() => onChange(!checked)}><span /></button>;
}

function SettingRow({ icon, label, value, onChange }: { icon: React.ReactNode; label: string; value: boolean; onChange: (value: boolean) => void }) {
  return <div className="setting-row"><div className="setting-icon">{icon}</div><span>{label}</span><Switch checked={value} onChange={onChange} label={label} /></div>;
}

function IntervalRow({ value, onChange, label, paused, screenOn, everyMinutes }: { value: number; onChange: (value: number) => void; label: string; paused: string; screenOn: string; everyMinutes: (minutes: number) => string }) {
  return <label className="setting-row interval-row"><div className="setting-icon"><Smartphone /></div><span>{label}</span><select value={value} onChange={event => onChange(Number(event.target.value))} aria-label={label}>
    <option value={0}>{paused}</option>
    <option value={-1}>{screenOn}</option>
    {[5, 15, 30, 60, 180, 360, 720].map(minutes => <option value={minutes} key={minutes}>{everyMinutes(minutes)}</option>)}
  </select></label>;
}

export default function App() {
  const [state, setState] = useState(initial);
  const [tab, setTab] = useState<"home" | "settings" | "gallery" | "editor" | "immich">("home");
  const [busy, setBusy] = useState(false);
  const [notice, setNotice] = useState("");
  const [language, setLanguage] = useState<Language>(initialLanguage);
  const [gallery, setGallery] = useState<GalleryImage[]>([]);
  const [galleryTotal, setGalleryTotal] = useState(0);
  const [galleryLoading, setGalleryLoading] = useState(false);
  const [selectedIds, setSelectedIds] = useState<Set<string>>(() => new Set());
  const [editing, setEditing] = useState<GalleryImage | null>(null);
  const [immichConnection, setImmichConnection] = useState<ImmichConnection>({ configured: false, serverUrl: "", userName: "" });
  const [immichUrl, setImmichUrl] = useState("");
  const [immichApiKey, setImmichApiKey] = useState("");
  const [immichAlbums, setImmichAlbums] = useState<ImmichAlbum[]>([]);
  const [immichAlbumId, setImmichAlbumId] = useState("");
  const [immichAssets, setImmichAssets] = useState<ImmichAsset[]>([]);
  const [immichSelected, setImmichSelected] = useState<Set<string>>(() => new Set());
  const [immichPage, setImmichPage] = useState(1);
  const [immichHasMore, setImmichHasMore] = useState(false);
  const [immichLoading, setImmichLoading] = useState(false);
  const [immichImportProgress, setImmichImportProgress] = useState<ImmichImportProgress | null>(null);
  const phonePreviewRef = useRef<HTMLDivElement>(null);
  const phoneImageRef = useRef<HTMLImageElement>(null);
  const previewPointers = useRef(new Map<number, { x: number; y: number }>());
  const previewGesture = useRef<null | { centerX: number; centerY: number; distance: number; x: number; y: number; zoom: number; left: number; top: number; width: number; height: number; baseWidth: number; baseHeight: number }>(null);
  const galleryScrollPosition = useRef(0);
  const restoreGalleryScroll = useRef(false);

  useEffect(() => {
    const refresh = () => { void getState().then(setState).catch(() => undefined); };
    refresh();
    const onVisibilityChange = () => { if (!document.hidden) refresh(); };
    document.addEventListener("visibilitychange", onVisibilityChange);
    window.addEventListener("focus", refresh);
    return () => {
      document.removeEventListener("visibilitychange", onVisibilityChange);
      window.removeEventListener("focus", refresh);
    };
  }, []);
  useEffect(() => {
    getImmichConnection().then(connection => {
      setImmichConnection(connection);
      setImmichUrl(connection.serverUrl);
    }).catch(() => undefined);
  }, []);
  useEffect(() => {
    if (!notice) return;
    const timeout = window.setTimeout(() => setNotice(""), 3000);
    return () => window.clearTimeout(timeout);
  }, [notice]);
  useEffect(() => {
    setGallery(previous => previous.map(image => ({ ...image, selected: image.id === state.currentId })));
  }, [state.currentId]);
  useEffect(() => {
    window.localStorage.setItem("wallpaperflow-language", language);
    document.documentElement.lang = language;
    document.title = "WallpaperFlow";
  }, [language]);
  useLayoutEffect(() => {
    if (tab !== "editor" && tab !== "gallery") return;
    const top = tab === "gallery" && restoreGalleryScroll.current ? galleryScrollPosition.current : 0;
    restoreGalleryScroll.current = false;
    window.scrollTo(0, top);
    const frame = window.requestAnimationFrame(() => window.scrollTo(0, top));
    const settled = window.setTimeout(() => window.scrollTo(0, top), 120);
    return () => { window.cancelAnimationFrame(frame); window.clearTimeout(settled); };
  }, [tab]);
  const t = translations[language];
  const it = immichTranslations[language];
  const previewDate = useMemo(() => new Intl.DateTimeFormat(language, { weekday: "long", day: "numeric", month: "long" }).format(new Date()), [language]);
  const currentPreviewIndex = state.imageIds.indexOf(state.currentId ?? "");
  const current = state.imageUrls[currentPreviewIndex] ?? "/wallpapers/alpine.png";
  const photos = useMemo(() => state.imageUrls, [state.imageUrls]);

  async function loadGallery(offset = 0, append = false) {
    setGalleryLoading(true);
    try {
      const page = await getGallery(offset, 48);
      setGallery(previous => append ? [...previous, ...page.items] : page.items);
      setGalleryTotal(page.total);
    } catch { setNotice(t.galleryLoadFailed); }
    finally { setGalleryLoading(false); }
  }

  async function update(name: "shuffle" | "lockScreenOnly" | "allowMobileData" | "prefetchImmich", value: boolean) {
    const previous = state[name];
    setState(prev => ({ ...prev, [name]: value }));
    try {
      const saved = await setSetting(name, value);
      setState(saved);
      setNotice(t.settingSaved);
    } catch (error) {
      setState(prev => ({ ...prev, [name]: previous }));
      setNotice(String(error).replace(/^Error:\s*/, "") || t.androidOnly);
    }
  }

  async function updateInterval(minutes: number) {
    setState(prev => ({ ...prev, intervalMinutes: minutes, enabled: minutes !== 0 }));
    try {
      const saved = await setIntervalMinutes(minutes);
      setState(saved);
      setNotice(minutes !== 0 ? t.intervalSaved : t.automaticDisabled);
    }
    catch { setNotice(t.androidOnly); }
  }

  async function choose() {
    setBusy(true);
    try {
      setState(await selectImages());
      if (tab === "gallery") await loadGallery();
      setNotice(t.imagesAdded);
    } catch (error) { setNotice(String(error).includes("cancel") ? t.selectionCancelled : t.imageSelectionAndroid); }
    finally { setBusy(false); }
  }

  function toggleSelected(id: string) {
    setSelectedIds(previous => {
      const next = new Set(previous);
      if (next.has(id)) next.delete(id); else next.add(id);
      return next;
    });
  }

  async function selectAll() {
    if (selectedIds.size === galleryTotal) { setSelectedIds(new Set()); return; }
    setBusy(true);
    try { setSelectedIds(new Set(await getImageIds())); }
    catch { setNotice(t.galleryLoadFailed); }
    finally { setBusy(false); }
  }

  async function removeSelected() {
    if (!selectedIds.size || !window.confirm(t.deleteSelectedConfirm)) return;
    setBusy(true);
    try {
      setState(await deleteImages([...selectedIds]));
      setSelectedIds(new Set());
      await loadGallery();
      setNotice(t.imagesDeleted);
    } catch { setNotice(t.imagesDeleteFailed); }
    finally { setBusy(false); }
  }

  function openEditor(image: GalleryImage) {
    galleryScrollPosition.current = window.scrollY;
    setEditing({ ...image, cropZoom: Math.max(1, image.cropZoom) });
    setTab("editor");
  }

  function returnToGallery() {
    restoreGalleryScroll.current = true;
    setTab("gallery");
  }

  function openGallery() {
    galleryScrollPosition.current = 0;
    restoreGalleryScroll.current = false;
    setTab("gallery");
    void loadGallery();
  }

  async function saveImmichConnection() {
    setBusy(true);
    try {
      const connection = await connectImmich(immichUrl, immichApiKey);
      setImmichConnection(connection);
      setImmichUrl(connection.serverUrl);
      setImmichApiKey("");
      setNotice(it.connectionSuccess);
    } catch (error) { setNotice(String(error).replace(/^Error:\s*/, "") || it.connectionFailed); }
    finally { setBusy(false); }
  }

  async function removeImmichConnection() {
    setBusy(true);
    try {
      setImmichConnection(await disconnectImmich());
      setImmichUrl("");
      setImmichApiKey("");
      setImmichAssets([]);
      setImmichAlbums([]);
      setImmichSelected(new Set());
      setNotice(it.disconnected);
    } catch { setNotice(it.connectionFailed); }
    finally { setBusy(false); }
  }

  async function loadImmichPhotos(albumId: string, page = 1, append = false) {
    setImmichLoading(true);
    try {
      const result = await getImmichAssets(albumId || null, page, 30);
      setImmichAssets(previous => append ? [...previous, ...result.items] : result.items);
      setImmichPage(result.page);
      setImmichHasMore(result.hasMore);
    } catch (error) { setNotice(String(error).replace(/^Error:\s*/, "") || it.immichLoadFailed); }
    finally { setImmichLoading(false); }
  }

  async function openImmich() {
    setTab("immich");
    setImmichSelected(new Set());
    setImmichLoading(true);
    try {
      const albums = await getImmichAlbums();
      setImmichAlbums(albums);
      await loadImmichPhotos(immichAlbumId);
    } catch (error) {
      setNotice(String(error).replace(/^Error:\s*/, "") || it.immichLoadFailed);
      setImmichLoading(false);
    }
  }

  function toggleImmichAsset(id: string) {
    setImmichSelected(previous => {
      const next = new Set(previous);
      if (next.has(id)) next.delete(id); else next.add(id);
      return next;
    });
  }

  async function importFromImmich() {
    if (!immichSelected.size) return;
    setBusy(true);
    setImmichImportProgress({ active: true, completed: 0, total: immichSelected.size, bytesDownloaded: 0, bytesTotal: 0 });
    setNotice(it.importing);
    const poll = window.setInterval(() => {
      void getImmichImportProgress().then(setImmichImportProgress).catch(() => undefined);
    }, 750);
    try {
      setState(await importImmichAssets([...immichSelected]));
      setImmichSelected(new Set());
      setNotice(it.importSuccess);
      setTab("home");
    } catch (error) { setNotice(String(error).replace(/^Error:\s*/, "") || it.immichLoadFailed); }
    finally {
      window.clearInterval(poll);
      setBusy(false);
      setImmichImportProgress(null);
    }
  }

  async function saveCrop() {
    if (!editing) return;
    setBusy(true);
    try {
      const saved = await setImageCrop(editing);
      setGallery(previous => previous.map(image => image.id === saved.id ? saved : image));
      setEditing(saved);
      setNotice(t.cropSaved);
      returnToGallery();
    } catch { setNotice(t.cropSaveFailed); }
    finally { setBusy(false); }
  }

  function gestureGeometry() {
    const points = [...previewPointers.current.values()];
    if (!points.length) return { centerX: 0, centerY: 0, distance: 0 };
    if (points.length === 1) return { centerX: points[0].x, centerY: points[0].y, distance: 0 };
    const [first, second] = points;
    return {
      centerX: (first.x + second.x) / 2,
      centerY: (first.y + second.y) / 2,
      distance: Math.hypot(second.x - first.x, second.y - first.y),
    };
  }

  function gestureStart() {
    const preview = phonePreviewRef.current;
    const image = phoneImageRef.current;
    if (!editing || !preview || !image) return null;
    if (!image.naturalWidth || !image.naturalHeight) return null;
    const bounds = preview.getBoundingClientRect();
    const quarterTurn = editing.cropRotation === 90 || editing.cropRotation === 270;
    const imageWidth = quarterTurn ? image.naturalHeight : image.naturalWidth;
    const imageHeight = quarterTurn ? image.naturalWidth : image.naturalHeight;
    const scaleX = bounds.width / imageWidth;
    const scaleY = bounds.height / imageHeight;
    const baseScale = editing.cropMode === "contain" ? Math.min(scaleX, scaleY) : Math.max(scaleX, scaleY);
    return {
      ...gestureGeometry(),
      x: editing.cropPositionX,
      y: editing.cropPositionY,
      zoom: editing.cropZoom,
      left: bounds.left,
      top: bounds.top,
      width: bounds.width,
      height: bounds.height,
      baseWidth: imageWidth * baseScale,
      baseHeight: imageHeight * baseScale,
    };
  }

  function rotateEditing() {
    setEditing(previous => previous ? {
      ...previous,
      cropRotation: (previous.cropRotation + 90) % 360,
      cropZoom: 1,
      cropPositionX: 0.5,
      cropPositionY: 0.5,
    } : previous);
  }

  function previewPosition(image: GalleryImage) {
    const { cropPositionX: x, cropPositionY: y } = image;
    if (image.cropRotation === 90) return `${y * 100}% ${(1 - x) * 100}%`;
    if (image.cropRotation === 180) return `${(1 - x) * 100}% ${(1 - y) * 100}%`;
    if (image.cropRotation === 270) return `${(1 - y) * 100}% ${x * 100}%`;
    return `${x * 100}% ${y * 100}%`;
  }

  function beginPreviewGesture(event: React.PointerEvent) {
    if (!editing) return;
    event.preventDefault();
    phonePreviewRef.current?.setPointerCapture(event.pointerId);
    previewPointers.current.set(event.pointerId, { x: event.clientX, y: event.clientY });
    previewGesture.current = gestureStart();
  }

  function movePreviewGesture(event: React.PointerEvent) {
    if (!previewPointers.current.has(event.pointerId)) return;
    event.preventDefault();
    previewPointers.current.set(event.pointerId, { x: event.clientX, y: event.clientY });
    const gesture = previewGesture.current;
    if (!gesture) return;
    const geometry = gestureGeometry();
    const zoom = geometry.distance && gesture.distance
      ? Math.min(3, Math.max(1, gesture.zoom * geometry.distance / gesture.distance))
      : gesture.zoom;
    const previousLeft = (gesture.width - gesture.baseWidth * gesture.zoom) * gesture.x;
    const previousTop = (gesture.height - gesture.baseHeight * gesture.zoom) * gesture.y;
    const imageX = (gesture.centerX - gesture.left - previousLeft) / gesture.zoom;
    const imageY = (gesture.centerY - gesture.top - previousTop) / gesture.zoom;
    const nextLeft = geometry.centerX - gesture.left - imageX * zoom;
    const nextTop = geometry.centerY - gesture.top - imageY * zoom;
    const horizontalTravel = gesture.width - gesture.baseWidth * zoom;
    const verticalTravel = gesture.height - gesture.baseHeight * zoom;
    const x = Math.abs(horizontalTravel) < 0.5 ? 0.5 : nextLeft / horizontalTravel;
    const y = Math.abs(verticalTravel) < 0.5 ? 0.5 : nextTop / verticalTravel;
    setEditing(previous => previous ? {
      ...previous,
      cropZoom: zoom,
      cropPositionX: Math.min(1, Math.max(0, x)),
      cropPositionY: Math.min(1, Math.max(0, y)),
    } : previous);
  }

  function endPreviewGesture(event: React.PointerEvent) {
    previewPointers.current.delete(event.pointerId);
    if (phonePreviewRef.current?.hasPointerCapture(event.pointerId)) phonePreviewRef.current.releasePointerCapture(event.pointerId);
    if (!editing || !previewPointers.current.size) {
      previewGesture.current = null;
      return;
    }
    previewGesture.current = gestureStart();
  }

  async function next() {
    setBusy(true);
    try { setState(await nextWallpaper()); setNotice(t.wallpaperUpdated); }
    catch (error) { setNotice(String(error).replace(/^Error:\s*/, "")); }
    finally { setBusy(false); }
  }

  async function selectWallpaper(id: string) {
    if (busy || id === state.currentId) return;
    setBusy(true);
    try { setState(await applyWallpaper(id)); setNotice(t.wallpaperUpdated); }
    catch (error) { setNotice(String(error).replace(/^Error:\s*/, "")); }
    finally { setBusy(false); }
  }

  return <main className="app-shell">
    {tab === "gallery" ? <header className="gallery-header"><button className="icon-button" aria-label={t.back} onClick={() => { setSelectedIds(new Set()); setTab("home"); }}><ArrowLeft /></button><div><h1>{selectedIds.size ? `${selectedIds.size} ${t.selected}` : t.collection}</h1><p>{galleryTotal} {galleryTotal === 1 ? t.image : t.images}</p></div>{selectedIds.size ? <button className="icon-button delete-selection" aria-label={t.deleteSelected} onClick={removeSelected} disabled={busy}><Trash2 /></button> : <button className="icon-button" aria-label={t.addImages} onClick={choose} disabled={busy}><Plus /></button>}</header> : tab === "editor" ?
      <header className="gallery-header"><button className="icon-button" aria-label={t.backToGallery} onClick={returnToGallery}><ArrowLeft /></button><div><h1>{t.editImage}</h1><p>{t.savedForImage}</p></div><button className="icon-button save-crop" aria-label={t.saveCrop} onClick={saveCrop} disabled={busy}><Save /></button></header> :
      tab === "immich" ? <header className="gallery-header immich-header"><button className="icon-button" aria-label={t.back} onClick={() => { setImmichSelected(new Set()); setTab("settings"); }}><ArrowLeft /></button><div><h1>{it.immich}</h1><p>{immichSelected.size ? `${immichSelected.size} ${t.selected}` : it.selectImmichPhotos}</p></div>{immichSelected.size > 0 && <button className="icon-button import-selection" aria-label={it.importSelected} onClick={importFromImmich} disabled={busy}><CloudDownload /></button>}</header> :
      <header className="app-header"><div className="brand"><img className="brand-logo" src="/app-icon.svg" alt="" /><div className="brand-copy"><h1>WallpaperFlow</h1><p>{state.enabled ? t.automaticActive : t.automaticPaused}</p></div></div><button className={`icon-button settings-shortcut ${tab === "settings" ? "selected" : ""}`} aria-label={t.settings} aria-current={tab === "settings" ? "page" : undefined} onClick={() => setTab("settings")}><Settings /></button></header>}

    <div className="content">
      {tab === "home" ? <>
        <section className="hero" aria-label={t.currentWallpaper}>
          <div className="photo-stack" />
          <img src={current} alt={t.currentImage} />
          <div className="hero-shade" />
          <div className="hero-meta"><span><Sparkles size={16} /> {t.currentImage}</span><button onClick={next} disabled={busy}>{t.nextImage} <ChevronRight size={18} /></button></div>
        </section>

        <IntervalRow value={state.intervalMinutes} onChange={updateInterval} label={t.changeInterval} paused={t.paused} screenOn={t.screenOn} everyMinutes={t.everyMinutes} />

        <button className="primary" onClick={choose} disabled={busy}><Images />{busy ? t.pleaseWait : t.selectImages}</button>

        <section className="collection"><div className="section-heading"><h2>{t.collection}</h2><button onClick={openGallery}>{state.imageCount} {state.imageCount === 1 ? t.image : t.images} <ChevronRight /></button></div>
          {photos.length ? <div className="photo-rail">{photos.map((photo, index) => <button key={state.imageIds[index] ?? `${photo}-${index}`} className={state.imageIds[index] === state.currentId ? "selected" : ""} disabled={busy} onClick={() => void selectWallpaper(state.imageIds[index])}><img src={photo} alt={`${t.motif} ${index + 1}`} />{state.imageIds[index] === state.currentId && <span><Check /></span>}</button>)}</div> : <button className="empty-collection" onClick={choose}><Images /><span>{t.noImages}</span></button>}
          <p className="hint">{t.collectionHint}</p>
        </section>

        <section className="settings-list"><SettingRow icon={<Shuffle />} label={t.shuffle} value={state.shuffle} onChange={v => update("shuffle", v)} /><SettingRow icon={<LockKeyhole />} label={t.lockScreenOnly} value={state.lockScreenOnly} onChange={v => update("lockScreenOnly", v)} /></section>
      </> : tab === "settings" ? <section className="settings-page">
        <h2>{t.settings}</h2><p>{t.settingsIntro}</p>
        <label className="language-setting"><div className="setting-icon"><Languages /></div><div><strong>{t.language}</strong><span>{languageNames[language]}</span></div><select className="locale-select" value={language} onChange={event => setLanguage(event.target.value as Language)} aria-label={t.language}>{languages.map(locale => <option value={locale} key={locale}>{languageNames[locale]}</option>)}</select></label>
        <div className="settings-list"><IntervalRow value={state.intervalMinutes} onChange={updateInterval} label={t.changeInterval} paused={t.paused} screenOn={t.screenOn} everyMinutes={t.everyMinutes} /><SettingRow icon={<Shuffle />} label={t.shuffle} value={state.shuffle} onChange={v => update("shuffle", v)} /><SettingRow icon={<LockKeyhole />} label={t.lockScreenOnly} value={state.lockScreenOnly} onChange={v => update("lockScreenOnly", v)} /></div>
        <section className="immich-settings">
          <div className="immich-title"><span><Cloud /></span><div><h3>{it.immich}</h3><p>{it.immichIntro}</p></div></div>
          {immichConnection.configured ? <>
            <div className="immich-connected"><strong>{it.connectedAs} {immichConnection.userName}</strong><span>{immichConnection.serverUrl}</span></div>
            <SettingRow icon={<Download />} label={it.prefetchImmich} value={state.prefetchImmich} onChange={v => update("prefetchImmich", v)} />
            <p className="immich-help">{it.prefetchImmichHint}</p>
            <SettingRow icon={<Wifi />} label={it.allowMobileData} value={state.allowMobileData} onChange={v => update("allowMobileData", v)} />
            <p className="immich-help">{it.allowMobileDataHint}</p>
            <button className="immich-open" onClick={openImmich} disabled={busy}><Images /> {it.openImmich} <ChevronRight /></button>
            <button className="immich-disconnect" onClick={removeImmichConnection} disabled={busy}><Link2Off /> {it.disconnect}</button>
          </> : <>
            <label className="immich-field"><span>{it.serverUrl}</span><div><Server /><input type="url" inputMode="url" autoCapitalize="none" autoCorrect="off" value={immichUrl} onChange={event => setImmichUrl(event.target.value)} placeholder="https://photos.example.com" /></div></label>
            {immichUrl.trim().toLowerCase().startsWith("http://") && <p className="immich-warning">{it.httpWarning}</p>}
            <label className="immich-field"><span>{it.apiKey}</span><input type="password" autoCapitalize="none" autoCorrect="off" value={immichApiKey} onChange={event => setImmichApiKey(event.target.value)} placeholder={it.apiKeyPlaceholder} /></label>
            <p className="immich-help">{it.apiKeyHelp}</p>
            <button className="immich-connect" onClick={saveImmichConnection} disabled={busy || !immichUrl.trim() || !immichApiKey.trim()}><Cloud /> {busy ? t.pleaseWait : it.connect}</button>
          </>}
        </section>
        <div className="info"><h3>{t.noImageLimit}</h3><p>{t.privacyInfo}</p></div>
      </section> : tab === "immich" ?
        <section className="immich-browser">
          <label className="immich-album-filter"><span>{it.immich}</span><select value={immichAlbumId} onChange={event => { const id = event.target.value; setImmichAlbumId(id); setImmichSelected(new Set()); void loadImmichPhotos(id); }}><option value="">{it.allPhotos}</option>{immichAlbums.map(album => <option value={album.id} key={album.id}>{album.name} ({album.assetCount})</option>)}</select></label>
          {!!immichAssets.length && <div className="selection-toolbar"><span>{immichSelected.size ? `${immichSelected.size} ${t.selected}` : it.selectImmichPhotos}</span><button onClick={() => setImmichSelected(immichSelected.size === immichAssets.length ? new Set() : new Set(immichAssets.map(asset => asset.id)))}>{immichSelected.size === immichAssets.length ? t.clearSelection : t.selectAll}</button></div>}
          {immichAssets.length ? <div className="gallery-grid immich-grid">{immichAssets.map((asset, index) => <article className={immichSelected.has(asset.id) ? "chosen" : ""} key={asset.id}><button className="gallery-image-button" aria-label={`${immichSelected.has(asset.id) ? t.deselectImage : t.selectImage} ${index + 1}`} aria-pressed={immichSelected.has(asset.id)} onClick={() => toggleImmichAsset(asset.id)}>{asset.thumbnailUrl ? <img src={asset.thumbnailUrl} alt={asset.fileName} /> : <span className="immich-placeholder"><Images /></span>}</button><span className="selection-check"><Check /></span></article>)}</div> : !immichLoading && <div className="gallery-empty"><Cloud /><h2>{it.noImmichPhotos}</h2></div>}
          {immichLoading && <p className="gallery-status">{t.galleryLoading}</p>}
          {!immichLoading && immichHasMore && <button className="load-more" onClick={() => loadImmichPhotos(immichAlbumId, immichPage + 1, true)}>{t.loadMore}</button>}
          {!!immichSelected.size && (busy && immichImportProgress ? <div className="immich-import-progress" role="status" aria-live="polite"><div><span>{it.importing}</span><strong>{immichImportProgress.completed} / {immichImportProgress.total}</strong></div><progress max={immichImportProgress.bytesTotal || immichImportProgress.total || 1} value={immichImportProgress.bytesTotal ? immichImportProgress.bytesDownloaded : immichImportProgress.completed} />{immichImportProgress.bytesTotal > 0 && <div><span>{Math.round(immichImportProgress.bytesDownloaded / immichImportProgress.bytesTotal * 100)}%</span><span>{(immichImportProgress.bytesDownloaded / 1024 / 1024).toFixed(1)} / {(immichImportProgress.bytesTotal / 1024 / 1024).toFixed(1)} MB</span></div>}</div> : <button className="primary immich-import" onClick={importFromImmich} disabled={busy}><CloudDownload /> {`${it.importSelected} (${immichSelected.size})`}</button>)}
        </section> : tab === "gallery" ?
        <section className="gallery-page" aria-label={t.myImages}>
          {!!galleryTotal && <div className="selection-toolbar"><span>{selectedIds.size ? `${selectedIds.size} ${t.selected}` : t.selectImagesToDelete}</span><button onClick={selectAll} disabled={busy}>{selectedIds.size === galleryTotal ? t.clearSelection : t.selectAll}</button></div>}
          {gallery.length ? <div className="gallery-grid">{gallery.map((image, index) => <article className={`${image.selected ? "current " : ""}${selectedIds.has(image.id) ? "chosen" : ""}`} key={image.id}><button className="gallery-image-button" aria-label={`${selectedIds.has(image.id) ? t.deselectImage : t.selectImage} ${index + 1}`} aria-pressed={selectedIds.has(image.id)} onClick={() => toggleSelected(image.id)}><img src={image.url} alt={`${t.image} ${index + 1}`} /></button>{image.selected && <span className="current-badge"><Check /> {t.current}</span>}<span className="selection-check"><Check /></span><button className="edit-button" aria-label={`${t.adjustImage} ${index + 1}`} onClick={() => openEditor(image)}><Crop /></button></article>)}</div> : !galleryLoading && <div className="gallery-empty"><Images /><h2>{t.emptyCollection}</h2><p>{t.emptyCollectionText}</p><button className="primary" onClick={choose}><Plus /> {t.addImages}</button></div>}
          {galleryLoading && <p className="gallery-status">{t.galleryLoading}</p>}
          {!galleryLoading && gallery.length < galleryTotal && <button className="load-more" onClick={() => loadGallery(gallery.length, true)}>{t.loadMore}</button>}
        </section> : editing && <section className="crop-editor" aria-label={t.editCrop}>
          <div ref={phonePreviewRef} className="phone-preview interactive" aria-label={t.gestureLabel} onPointerDown={beginPreviewGesture} onPointerMove={movePreviewGesture} onPointerUp={endPreviewGesture} onPointerCancel={endPreviewGesture}>
            <div className={`preview-image-frame ${editing.cropRotation === 90 || editing.cropRotation === 270 ? "quarter-turn" : ""}`} style={{ transform: `rotate(${editing.cropRotation}deg) scale(${editing.cropZoom})` }}><img ref={phoneImageRef} src={editing.url} alt={t.cropPreview} draggable={false} style={{ objectFit: editing.cropMode, objectPosition: previewPosition(editing) }} /></div>
            <div className="crop-grid" aria-hidden="true"><i /><i /><i /><i /></div>
            <div className="preview-clock">12:34<span>{previewDate}</span></div>
          </div>
          <div className="crop-controls">
            <div className="fit-toggle" aria-label={t.imageFit}><button className={editing.cropMode === "cover" ? "active" : ""} onClick={() => setEditing({ ...editing, cropMode: "cover", cropZoom: 1, cropPositionX: 0.5, cropPositionY: 0.5 })}>{t.fill}</button><button className={editing.cropMode === "contain" ? "active" : ""} onClick={() => setEditing({ ...editing, cropMode: "contain", cropZoom: 1, cropPositionX: 0.5, cropPositionY: 0.5 })}>{t.fit}</button></div>
            <button className="rotate-image" onClick={rotateEditing}><RotateCw /> {t.rotate} <strong>{editing.cropRotation}°</strong></button>
            <div className="direct-crop-heading"><span><Crop /> {t.adjustOnScreen}</span><strong>{editing.cropZoom.toFixed(2)}×</strong></div>
            <p className="direct-crop-help">{t.gestureHelp}</p>
            <label className="zoom-control"><span>{t.zoom}</span><input type="range" min="1" max="3" step="0.01" value={editing.cropZoom} onChange={event => setEditing({ ...editing, cropZoom: Number(event.target.value) })} /></label>
            <button className="reset-crop" onClick={() => setEditing({ ...editing, cropMode: "cover", cropZoom: 1, cropPositionX: 0.5, cropPositionY: 0.5, cropRotation: 0 })}><RotateCcw /> {t.reset}</button>
          </div>
        </section>}
    </div>

    {notice && <button className="snackbar" onClick={() => setNotice("")}>{notice}</button>}
    {tab !== "gallery" && tab !== "editor" && tab !== "immich" && <nav><button className={tab === "home" ? "active" : ""} onClick={() => setTab("home")}><Home /><span>{t.home}</span></button><button className={tab === "settings" ? "active" : ""} onClick={() => setTab("settings")}><Settings /><span>{t.settings}</span></button></nav>}
  </main>;
}
