'use client';

import { type CSSProperties, useEffect, useMemo, useRef, useState } from 'react';

import { createLocalStorageAdapter } from '../adapters/localStorage';
import type { StorageAdapter } from '../adapters/types';
import type { AIProvider } from '../ai/types';
import { normalizeMediaItem, type MediaInput } from '../lib/media';
import { GalleryStoreContext, useGallery, useGalleryStoreApi } from '../store/context';
import {
  createGalleryStore,
  DEFAULT_CHROME,
  DEFAULT_FEATURES,
  type GalleryConfig,
  type GalleryFeatures,
  type GalleryStore,
  type ThemeTokens,
} from '../store/store';
import type { Album, GalleryUser, MediaItem, ThemePreference, ViewId } from '../types';
import { AIAnalyzer } from './AIAnalyzer';
import { SemanticSearch } from './SemanticSearch';
import { AIProviderContext } from './aiContext';
import { AppShell } from './AppShell';
import { Camera } from './Camera';
import { ContextMenuHost } from './ContextMenu';
import { PhotoEditor } from './editor/PhotoEditor';
import { VideoEditor } from './editor/VideoEditor';
import { InfoPanel } from './InfoPanel';
import { Lightbox } from './Lightbox';
import { ModalHost } from './Modal';

export interface PhotoGalleryProps {
  /** Initial media. Accepts full MediaItems or loose `{ src, name?, ... }` inputs. */
  photos?: Array<MediaItem | MediaInput>;
  /** Initial user albums. */
  albums?: Album[];
  /** Storage backend. Defaults to a zero-config localStorage adapter. */
  adapter?: StorageAdapter;
  /** AI provider for object/face/caption/search. Pass `false` to disable. */
  ai?: AIProvider | boolean;
  theme?: ThemePreference;
  accentColor?: string;
  /** Base corner radius in px (default 10). Drives all rounded UI. */
  borderRadius?: number;
  /** Per-theme color / gradient / radius overrides (mapped to CSS variables). */
  themeTokens?: ThemeTokens;
  features?: Partial<GalleryFeatures>;
  /** Render the macOS-style traffic-light title bar. Maps onto `chrome.titlebar`. */
  showWindowChrome?: boolean;
  title?: string;
  /**
   * The host app's signed-in user. When set, comments are stamped with this
   * identity (avatar + name + id) instead of a free-text author field, and only
   * the user's own comments show a delete affordance.
   */
  currentUser?: GalleryUser;
  /** Base URL for generated share links (default `${location.origin}/gallery`). */
  shareBaseUrl?: string;
  /** Suppress pieces of the gallery's own chrome that the host already provides. */
  chrome?: Partial<GalleryConfig['chrome']>;
  /**
   * Sidebar rows + Collections sections to hide (by ViewId).
   * e.g. `['screenshots', 'sys:documents']`. Default `[]` (nothing hidden).
   */
  hiddenViews?: ViewId[];
  /** Bind global keyboard shortcuts to `window` (default true). */
  keyboardShortcuts?: boolean;
  /**
   * Embedded mode: the gallery fills its host container (height 100%) instead of
   * assuming a full-viewport parent, and interactive elements use `cursor: pointer`.
   */
  embedded?: boolean;
  /**
   * Server-backed, per-user lock for the Recently Deleted view. When supplied the
   * SDK uses this INSTEAD of its device-local localStorage hash.
   */
  lockProvider?: {
    status(): Promise<{ hasPassword: boolean }>;
    set(password: string | null): Promise<void>; // null clears it
    verify(password: string): Promise<boolean>;
  };
  /** Start the gallery maximised (default false). */
  defaultFullscreen?: boolean;
  className?: string;
  style?: CSSProperties;
  onReady?: () => void;
}

export function PhotoGallery(props: PhotoGalleryProps) {
  const {
    photos,
    albums,
    adapter,
    ai,
    theme = 'system',
    accentColor,
    borderRadius,
    themeTokens,
    features,
    showWindowChrome = false,
    title = 'Photos',
    currentUser,
    shareBaseUrl,
    chrome,
    hiddenViews,
    keyboardShortcuts = true,
    embedded = false,
    lockProvider,
    defaultFullscreen = false,
    className,
    style,
    onReady,
  } = props;

  // Resolve adapter / AI once.
  const adapterRef = useRef<StorageAdapter | null>(null);
  if (!adapterRef.current) {
    adapterRef.current = adapter ?? createLocalStorageAdapter();
  }
  const aiProvider: AIProvider | null = useMemo(
    () => (ai && typeof ai === 'object' ? ai : null),
    [ai],
  );

  // Destructure chrome to primitives so an inline `chrome={{…}}` literal doesn't
  // recompute the config on every host render. `showWindowChrome` is the legacy
  // spelling of `chrome.titlebar`; an explicit `chrome.titlebar` wins.
  const chromeTitlebar = chrome?.titlebar ?? showWindowChrome;
  const chromeSidebar = chrome?.sidebar ?? DEFAULT_CHROME.sidebar;
  const chromeToolbar = chrome?.toolbar ?? DEFAULT_CHROME.toolbar;
  const chromeThemeSwitcher = chrome?.themeSwitcher ?? DEFAULT_CHROME.themeSwitcher;
  const userId = currentUser?.id;
  const userName = currentUser?.name;
  const userEmail = currentUser?.email;
  const userAvatar = currentUser?.avatarUrl;
  // Stable key so an inline `hiddenViews={['screenshots']}` literal doesn't
  // recompute the config on every host render.
  const hiddenViewsKey = (hiddenViews ?? []).join(',');

  const config: GalleryConfig = useMemo(
    () => ({
      features: { ...DEFAULT_FEATURES, ...features },
      accentColor: themeTokens?.accent ?? accentColor ?? '#0a84ff',
      borderRadius: borderRadius ?? 10,
      showWindowChrome: chromeTitlebar,
      title,
      themeTokens,
      currentUser:
        userId !== undefined
          ? { id: userId, name: userName ?? '', email: userEmail, avatarUrl: userAvatar }
          : undefined,
      shareBaseUrl,
      chrome: {
        titlebar: chromeTitlebar,
        sidebar: chromeSidebar,
        toolbar: chromeToolbar,
        themeSwitcher: chromeThemeSwitcher,
      },
      hiddenViews: hiddenViewsKey ? (hiddenViewsKey.split(',') as ViewId[]) : [],
      keyboardShortcuts,
      embedded,
      lockProvider,
      defaultFullscreen,
    }),
    [
      features,
      accentColor,
      borderRadius,
      themeTokens,
      title,
      userId,
      userName,
      userEmail,
      userAvatar,
      shareBaseUrl,
      chromeTitlebar,
      chromeSidebar,
      chromeToolbar,
      chromeThemeSwitcher,
      hiddenViewsKey,
      keyboardShortcuts,
      embedded,
      lockProvider,
      defaultFullscreen,
    ],
  );

  // Create the store exactly once for this gallery instance.
  const [store] = useState<GalleryStore>(() =>
    createGalleryStore({
      config,
      // Normalize + sanitize every input (loose or full MediaItem) through one path.
      initialMedia: (photos ?? [])
        .map((p) => normalizeMediaItem(p))
        .filter((m): m is MediaItem => m !== null),
      initialAlbums: albums ?? [],
    }),
  );

  // Apply the initial theme preference into the store.
  useEffect(() => {
    store.getState().setTheme(theme);
  }, [store, theme]);

  // Keep the live config in sync with the props. The store is created once, so
  // without this a host changing theme tokens / user / chrome would have no effect.
  useEffect(() => {
    store.getState().setConfig(config);
  }, [store, config]);

  return (
    <GalleryStoreContext.Provider value={store}>
      <GalleryRoot
        adapter={adapterRef.current}
        ai={aiProvider}
        className={className}
        style={style}
        onReady={onReady}
      />
    </GalleryStoreContext.Provider>
  );
}

interface GalleryRootProps {
  adapter: StorageAdapter;
  ai: AIProvider | null;
  className?: string;
  style?: CSSProperties;
  onReady?: () => void;
}

function GalleryRoot({ adapter, ai, className, style, onReady }: GalleryRootProps) {
  const api = useGalleryStoreApi();
  const ready = useGallery((s) => s.ready);
  const theme = useGallery((s) => s.theme);
  const resolvedTheme = useGallery((s) => s.resolvedTheme);
  const accent = useGallery((s) => s.config.accentColor);
  const radius = useGallery((s) => s.config.borderRadius);
  const tokens = useGallery((s) => s.config.themeTokens);
  const showChrome = useGallery((s) => s.config.chrome.titlebar);
  const embedded = useGallery((s) => s.config.embedded);
  const fullscreen = useGallery((s) => s.fullscreen);
  const aiEnabled = useGallery((s) => s.config.features.ai);
  const cameraEnabled = useGallery((s) => s.config.features.camera);

  // Initialize (load persisted state) EXACTLY once — the ref guard prevents a
  // double init() (React Strict Mode / remounts) from re-seeding an empty backend.
  const initedRef = useRef(false);
  useEffect(() => {
    if (initedRef.current) return;
    initedRef.current = true;
    void api.getState().init(adapter, ai);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  useEffect(() => {
    if (ready) onReady?.();
  }, [ready, onReady]);

  // Resolve "system" theme and react to OS changes.
  useEffect(() => {
    if (typeof window === 'undefined' || !window.matchMedia) {
      api.getState().setResolvedTheme(theme === 'system' ? 'light' : theme);
      return;
    }
    if (theme !== 'system') {
      api.getState().setResolvedTheme(theme);
      return;
    }
    const mq = window.matchMedia('(prefers-color-scheme: dark)');
    const apply = () => api.getState().setResolvedTheme(mq.matches ? 'dark' : 'light');
    apply();
    mq.addEventListener('change', apply);
    return () => mq.removeEventListener('change', apply);
  }, [api, theme]);

  // Map optional theme tokens → CSS variables for the active theme. Dark values
  // apply in dark mode and to the semi-dark sidebar; light values elsewhere.
  const dark = resolvedTheme === 'dark';
  const tokenVars: Record<string, string> = {};
  if (tokens) {
    // Colors/gradients only — reject url()/expression()/JS or CSS breakout chars
    // (defense-in-depth; tokens normally come from build-time env, not user input).
    const CSS_UNSAFE = /(url\(|expression\(|javascript:|[<>{}])/i;
    const set = (v: string | undefined, name: string) => {
      if (v && !CSS_UNSAFE.test(v)) tokenVars[name] = v;
    };
    /** Pick the light/dark member of a token pair for the active theme. */
    const pick = (light: string | undefined, darkValue: string | undefined) =>
      dark ? darkValue : light;
    const px = (v: number | undefined, name: string) => {
      if (typeof v === 'number' && Number.isFinite(v)) tokenVars[name] = `${v}px`;
    };

    const bg = pick(tokens.bgLight, tokens.bgDark);
    set(bg, '--apg-bg');
    set(bg, '--apg-bg-content');
    set(pick(tokens.elevatedLight, tokens.elevatedDark), '--apg-bg-elevated');
    // In semi-dark the sidebar is always the dark glass value.
    set(
      resolvedTheme === 'semi-dark'
        ? tokens.sidebarBgDark
        : pick(tokens.sidebarBgLight, tokens.sidebarBgDark),
      '--apg-sidebar-bg',
    );
    set(pick(tokens.textLight, tokens.textDark), '--apg-text');
    px(tokens.sidebarRadius, '--apg-sidebar-radius');

    // ---- extended token map ----
    set(pick(tokens.accentStrongLight, tokens.accentStrongDark), '--apg-accent-strong');
    set(tokens.accentContrast, '--apg-accent-contrast');
    set(pick(tokens.dangerLight, tokens.dangerDark), '--apg-danger');
    set(pick(tokens.cardLight, tokens.cardDark), '--apg-card');
    set(pick(tokens.cardHoverLight, tokens.cardHoverDark), '--apg-card-hover');
    set(pick(tokens.toolbarBgLight, tokens.toolbarBgDark), '--apg-toolbar-bg');
    set(pick(tokens.menuBgLight, tokens.menuBgDark), '--apg-menu-bg');
    set(pick(tokens.separatorLight, tokens.separatorDark), '--apg-separator');
    set(
      pick(tokens.separatorStrongLight, tokens.separatorStrongDark),
      '--apg-separator-strong',
    );
    set(pick(tokens.hoverLight, tokens.hoverDark), '--apg-hover');
    set(pick(tokens.activeLight, tokens.activeDark), '--apg-active');
    set(
      pick(tokens.sidebarSelectedLight, tokens.sidebarSelectedDark),
      '--apg-sidebar-selected',
    );
    set(pick(tokens.textSecondaryLight, tokens.textSecondaryDark), '--apg-text-secondary');
    set(pick(tokens.textTertiaryLight, tokens.textTertiaryDark), '--apg-text-tertiary');
    set(pick(tokens.glassBorderLight, tokens.glassBorderDark), '--apg-glass-border');
    set(tokens.fontFamily, '--apg-font');
    px(tokens.radiusMenu, '--apg-radius-menu');
    set(tokens.shadowSm, '--apg-shadow-sm');
    set(pick(tokens.shadowMdLight, tokens.shadowMdDark), '--apg-shadow-md');
    set(pick(tokens.shadowLgLight, tokens.shadowLgDark), '--apg-shadow-lg');
    set(tokens.tileFav, '--apg-tile-fav');
    set(tokens.overlayBg, '--apg-overlay-bg');
    set(tokens.editorBg, '--apg-editor-bg');
    set(tokens.segmentedActive, '--apg-segmented-active');
    px(tokens.sidebarWidth, '--apg-sidebar-w');
    px(tokens.toolbarHeight, '--apg-toolbar-h');
  }

  // Where full-screen overlays (the Info panel) start. A host with its own header
  // bar can push this down; default matches the standalone toolbar offset.
  const overlayTop = tokens?.toolbarHeight ? `${tokens.toolbarHeight + 12}px` : '64px';

  const rootStyle: CSSProperties = {
    ['--apg-accent' as string]: accent,
    ['--apg-radius' as string]: `${radius}px`,
    ['--apg-radius-sm' as string]: `${Math.max(2, Math.round(radius * 0.6))}px`,
    ['--apg-radius-lg' as string]: `${Math.round(radius * 1.4)}px`,
    ['--apg-radius-xl' as string]: `${Math.round(radius * 2)}px`,
    ['--apg-overlay-top' as string]: overlayTop,
    ...tokenVars,
    ...style,
  };

  return (
    <AIProviderContext.Provider value={aiEnabled ? ai : null}>
      <div
        className={[
          'apg',
          embedded ? 'apg--embedded' : '',
          fullscreen ? 'apg--fullscreen' : '',
          className,
        ]
          .filter(Boolean)
          .join(' ')}
        data-theme={resolvedTheme}
        style={rootStyle}
      >
        {showChrome ? <WindowChrome /> : null}
        <div className="apg__body">
          <AppShell />
        </div>
        <Lightbox />
        <PhotoEditor />
        <VideoEditor />
        <InfoPanel />
        {cameraEnabled ? <Camera /> : null}
        <ModalHost />
        <ContextMenuHost />
        {ai && aiEnabled ? <AIAnalyzer provider={ai} /> : null}
        {ai && aiEnabled ? <SemanticSearch provider={ai} /> : null}
      </div>
    </AIProviderContext.Provider>
  );
}

function WindowChrome() {
  const title = useGallery((s) => s.config.title);
  return (
    <div className="apg-titlebar">
      <div className="apg-traffic">
        <span className="apg-traffic__dot apg-traffic__dot--red" />
        <span className="apg-traffic__dot apg-traffic__dot--yellow" />
        <span className="apg-traffic__dot apg-traffic__dot--green" />
      </div>
      <div className="apg-titlebar__title">{title}</div>
    </div>
  );
}
