// ENSEMBLE - Virtual Talent for Property
// Browser SPA backed by the local Node/SQLite API.

const { useState, useEffect, useMemo, useCallback } = React;

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "brand": "ENSEMBLE",
  "accent": "#826A4B",
  "bgTone": "#F4EFE5",
  "typePair": "cormorant",
  "gridCols": 4
} /*EDITMODE-END*/;

const TYPE_PAIRS = {
  cormorant: {
    display: '"Cormorant Garamond", "Cormorant", Georgia, serif',
    body: '"Cormorant Garamond", "Cormorant", Georgia, serif',
    bodyWeight: 400,
    displayWeight: 300,
    tracking: '0.18em',
    label: 'Cormorant'
  },
  playfair: {
    display: '"Playfair Display", "Cormorant", Georgia, serif',
    body: '"Inter", system-ui, sans-serif',
    bodyWeight: 300,
    displayWeight: 400,
    tracking: '0.20em',
    label: 'Playfair + Inter'
  },
  ibarra: {
    display: '"Ibarra Real Nova", "Cormorant", Georgia, serif',
    body: '"Ibarra Real Nova", Georgia, serif',
    bodyWeight: 400,
    displayWeight: 400,
    tracking: '0.22em',
    label: 'Ibarra'
  },
  fraunces: {
    display: '"Fraunces", Georgia, serif',
    body: '"Inter", system-ui, sans-serif',
    bodyWeight: 300,
    displayWeight: 300,
    tracking: '0.16em',
    label: 'Fraunces + Inter'
  }
};

const BG_TONES = ['#F4EFE5', '#EFE7D8', '#E8DFD0', '#F6F2EB', '#EDE6D8'];
const ACCENTS = ['#826A4B', '#A8634A', '#4F5B47', '#2D241B', '#9A6B3F'];
const FAV_KEY = 'ensemble.shortlist';
const MODEL_TAGS = {
  age_group: ['25-30', '30-35', '35-40', '40-45', '45-50', '50-55', '55-60', '60+'],
  ethnicity: ['Caucasian', 'Mediterranean', 'Asian', 'Mixed Race', 'African', 'Islander', 'Indigenous'],
  build: ['Lean', 'Athletic', 'Curvy', 'Petite', 'Classic'],
  height: ['160cm', '165cm', '170cm', '175cm', '180cm', '185cm'],
  style: ['Runway', 'Commercial', 'Editorial', 'Sporty', 'Cosmopolitan', 'Elegant'],
};
const AVAILABILITY_STATUSES = ['available', 'pending', 'booked'];
const ROSTER_FILTERS = [
  { key: 'availability_status', label: 'AVAILABILITY', options: AVAILABILITY_STATUSES },
  { key: 'age_group', label: 'AGE GROUP', options: MODEL_TAGS.age_group },
  { key: 'ethnicity', label: 'ETHNICITY', options: MODEL_TAGS.ethnicity },
  { key: 'build', label: 'BUILD', options: MODEL_TAGS.build },
  { key: 'height', label: 'HEIGHT', options: MODEL_TAGS.height },
  { key: 'style', label: 'STYLE', options: MODEL_TAGS.style },
];

function cleanTagValue(value, allowed) {
  const seen = new Set();
  return String(value || '')
    .split(',')
    .map((item) => item.trim())
    .filter((item) => allowed.includes(item))
    .filter((item) => {
      if (seen.has(item)) return false;
      seen.add(item);
      return true;
    })
    .join(', ');
}

function parseHash() {
  const h = (location.hash || '#/').replace(/^#/, '');
  const parts = h.split('/').filter(Boolean);
  if (parts.length === 0) return { page: 'home' };
  if (parts[0] === 'roster') return { page: 'roster', category: parts[1] || 'women' };
  if (parts[0] === 'talent') return { page: 'talent', handle: parts[1] };
  if (parts[0] === 'shortlist') return { page: 'shortlist' };
  if (parts[0] === 'contact') return { page: 'contact' };
  return { page: 'home' };
}

function useRoute() {
  const [route, setRoute] = useState(parseHash());
  useEffect(() => {
    const on = () => {
      setRoute(parseHash());
      window.scrollTo(0, 0);
    };
    window.addEventListener('hashchange', on);
    return () => window.removeEventListener('hashchange', on);
  }, []);
  return route;
}

async function api(path, options = {}) {
  const res = await fetch(path, options);
  if (!res.ok) {
    const body = await res.json().catch(() => ({}));
    throw new Error(body.error || `Request failed: ${res.status}`);
  }
  return res.json();
}

function useModels() {
  const [models, setModels] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState('');

  const refresh = useCallback(async () => {
    setLoading(true);
    setError('');
    try {
      setModels(await api('/api/models'));
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  }, []);

  useEffect(() => {
    refresh();
  }, [refresh]);

  const replaceModel = useCallback((updated) => {
    setModels((items) => items.map((item) => item.id === updated.id ? updated : item));
  }, []);

  return { models, loading, error, refresh, replaceModel };
}

function useShortlist() {
  const [ids, setIds] = useState(() => {
    try {
      return JSON.parse(localStorage.getItem(FAV_KEY) || '[]');
    } catch {
      return [];
    }
  });

  useEffect(() => {
    localStorage.setItem(FAV_KEY, JSON.stringify(ids));
  }, [ids]);

  const toggle = useCallback((id) => {
    setIds((current) => current.includes(id) ? current.filter((item) => item !== id) : [...current, id]);
  }, []);

  return { ids, toggle };
}

function useAdminLogin() {
  const [user, setUser] = useState(null);
  const [error, setError] = useState('');

  useEffect(() => {
    api('/api/admin/session')
      .then((session) => setUser(session.authenticated ? { name: 'Admin', email: 'Admin mode' } : null))
      .catch(() => setUser(null));
  }, []);

  const login = useCallback(async (passcode) => {
    setError('');
    try {
      await api('/api/admin/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ passcode }),
      });
      setUser({ name: 'Admin', email: 'Admin mode' });
      return true;
    } catch (err) {
      setError(err.message);
      return false;
    }
  }, []);

  const logout = useCallback(async () => {
    await api('/api/admin/logout', { method: 'POST' }).catch(() => {});
    setUser(null);
    setError('');
  }, []);

  return { user, error, login, logout };
}

function TopNav({ brand, route, menuOpen, onMenuToggle }) {
  const cat = route.page === 'roster' ? route.category : null;
  return (
    <header className="topnav">
      <a className="brand" href="#/" aria-label="Home">{brand}</a>
      <nav className="cats">
        <a className={cat === 'women' ? 'on' : ''} href="#/roster/women">WOMEN</a>
        <a className={cat === 'men' ? 'on' : ''} href="#/roster/men">MEN</a>
      </nav>
      <div className="util">
        <button
          className={'menu-btn' + (menuOpen ? ' is-open' : '')}
          aria-label={menuOpen ? 'Close menu' : 'Menu'}
          aria-expanded={menuOpen}
          onClick={onMenuToggle}>
          <span></span><span></span><span></span>
        </button>
      </div>
    </header>
  );
}

function MenuOverlay({ open, onClose, brand, route, auth }) {
  useEffect(() => {
    if (!open) return;
    const onKey = (e) => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', onKey);
    document.documentElement.style.overflow = 'hidden';
    return () => {
      window.removeEventListener('keydown', onKey);
      document.documentElement.style.overflow = '';
    };
  }, [open, onClose]);

  const cat = route.page === 'roster' ? route.category : null;
  const go = (href) => (e) => {
    e.preventDefault();
    onClose();
    setTimeout(() => { location.hash = href; }, 60);
  };

  const NavLink = ({ href, label, active }) => (
    <a href={href} onClick={go(href)} className={'menu-link' + (active ? ' on' : '')}>{label}</a>
  );

  return (
    <div className={'menu-overlay' + (open ? ' is-open' : '')} aria-hidden={!open}>
      <div className="menu-inner">
        <button className="menu-close" type="button" onClick={onClose} aria-label="Close menu">
          CLOSE
        </button>
        <nav className="menu-col menu-col-l">
          <div className="menu-group">
            <NavLink href="#/" label="WHO WE ARE" active={false} />
            <div className="menu-about">
              <p className="menu-about-lead">A new model of talent representation.</p>
              <p className="menu-about-intro">Ensemble is a digital talent agency representing a curated roster of virtual models and personalities created for contemporary marketing, film and visual culture.</p>
              <div className="menu-about-grid">
                <p>Developed for the world's leading property brands, our talent can be cast across campaigns, architectural films, brand imagery and sales experiences with a level of consistency, flexibility and creative control that traditional production models cannot achieve.</p>
                <p>Each individual within the Ensemble roster is carefully developed with a distinct identity, appearance and presence, allowing brands to build long-term associations across multiple projects, developments and markets.</p>
              </div>
              <p className="menu-about-collab">Working in collaboration with our sister companies, Melody Studios and MR.P Studios, Ensemble combines advanced AI systems with world-class visualisation, filmmaking and creative direction to deliver production-ready talent for the next generation of property marketing.</p>
              <p className="menu-about-close">Our focus is simple: exceptional talent, thoughtfully crafted and available wherever the work demands.</p>
            </div>
          </div>
        </nav>
        <AdminLoginPanel auth={auth} />
        <div className="menu-brand">{brand}</div>
      </div>
    </div>
  );
}

function AdminLoginPanel({ auth }) {
  const [passcode, setPasscode] = useState('');

  const submit = async (event) => {
    event.preventDefault();
    if (await auth.login(passcode)) setPasscode('');
  };

  return (
    <section className="menu-login">
      {auth.user ? (
        <div className="login-profile">
          <div>
            <p>{auth.user.name}</p>
            <span>{auth.user.email}</span>
          </div>
          <button type="button" onClick={auth.logout}>SIGN OUT</button>
        </div>
      ) : (
        <form className="passcode-login" onSubmit={submit}>
          <p className="login-note">Enter the admin passcode.</p>
          <div className="passcode-row">
            <input
              type="password"
              value={passcode}
              onChange={(event) => setPasscode(event.target.value)}
              placeholder="Passcode"
              aria-label="Admin passcode"
              autoComplete="current-password"
            />
            <button type="submit" aria-label="Enter admin passcode">→</button>
          </div>
          {auth.error && <p className="login-error">{auth.error}</p>}
        </form>
      )}
    </section>
  );
}

function Footer() {
  return (
    <footer className="foot">
      <div className="foot-l">
        <input type="email" placeholder="email" aria-label="email" />
        <span className="foot-cta">KEEP UP TO DATE</span>
      </div>
      <div className="foot-r">
        <a href="#/contact">CONTACT</a>
        <span className="sep">|</span>
        <a href="#/">T&amp;C</a>
        <span className="ig" aria-hidden="true">◎</span>
      </div>
    </footer>
  );
}

function Placeholder({ model }) {
  return (
    <div className="tile-placeholder">
      <span>{(model.first || model.name || '?').charAt(0)}</span>
    </div>
  );
}

function statusLabel(status) {
  return String(status || 'available').toUpperCase();
}

function ModelTile({ model, models, height, onOpen, isFavourite, onToggleFavourite, showName = true, showFavourite = false, imageUrl, variant = 'card', isRoster = false }) {
  const tileImage = imageUrl || model.headshot_url;
  const hoverImage = model.hover_headshot_url;
  const availability = model.availability_status || 'available';
  const cardClass = `tile model-card ${variant === 'carousel' ? 'carousel-card' : ''}${isRoster && availability === 'booked' ? ' is-booked' : ''}`;
  return (
    <article className={cardClass} style={{ height }}>
      {tileImage
        ? (
          <>
            <img className="tile-photo" src={tileImage} alt={`${model.name} headshot`} />
            {hoverImage && <img className="tile-photo tile-photo-hover" src={hoverImage} alt="" />}
          </>
        )
        : <Placeholder model={model} />}
      {isRoster && <span className={`availability-tag status-${availability}`}>{statusLabel(availability)}</span>}
      {showFavourite && (
        <button
          className={'fav-btn' + (isFavourite ? ' is-favourite' : '')}
          aria-label={isFavourite ? `Remove ${model.name} from shortlist` : `Add ${model.name} to shortlist`}
          onClick={(e) => {
            e.stopPropagation();
            onToggleFavourite(model.id);
          }}>
          {isFavourite ? '♥' : '♡'}
        </button>
      )}
      <button className="tile-hit" onClick={() => onOpen(model)} aria-label={`Open ${model.name} details`}></button>
      <div className="tile-overlay card-overlay">
        {showName && (
          <span className="tile-name-wrap">
            <span className="tile-name">{model.first || model.name}</span>
          </span>
        )}
        <span className="tile-foot card-name-only">
          <span>{model.name}</span>
        </span>
      </div>
      {variant !== 'carousel' && (
        <div className="tile-stats-hover" aria-hidden="true">
          <Stat label="AGE" value={model.age_group} />
          <Stat label="ETHNICITY" value={model.ethnicity} />
          <Stat label="BUILD" value={model.build} />
          <Stat label="HEIGHT" value={model.height} />
          <Stat label="GENDER" value={model.gender} />
          <Stat label="STYLE" value={model.style} />
        </div>
      )}
    </article>
  );
}

function CarouselPlaceholder({ index }) {
  return (
    <article className="tile model-card carousel-placeholder">
      <Placeholder model={{ first: 'E', name: 'Featured model placeholder' }} />
      <div className="tile-overlay card-overlay">
        <span className="tile-meta">
          <span className="tile-idx">{String(index + 1).padStart(3, '0')}</span>
          <span className="tile-dot"></span>
        </span>
        <span className="tile-name-wrap">
          <span className="tile-name">Featured</span>
        </span>
        <span className="tile-foot card-name-only">
          <span>ADD FEATURED MODEL</span>
        </span>
      </div>
    </article>
  );
}

function Home({ models, onOpenModel }) {
  const heroSlides = useMemo(() => {
    const featured = models.filter((model) => model.featured);
    if (featured.length) {
      const selectedFeatured = featured.length > 3
        ? [...featured].sort(() => Math.random() - 0.5).slice(0, 3)
        : featured.slice(0, 3);
      return [
        ...selectedFeatured.map((model) => ({ type: 'model', model })),
        ...Array.from({ length: Math.max(0, 3 - featured.length) }, () => ({ type: 'placeholder' })),
      ].slice(0, 3);
    }
    const women = models.filter((m) => m.gender === 'women');
    const men = models.filter((m) => m.gender === 'men');
    const fallback = [women[1], men[7], women[8]].filter(Boolean);
    return [
      ...fallback.map((model) => ({ type: 'model', model })),
      ...Array.from({ length: Math.max(0, 3 - fallback.length) }, () => ({ type: 'placeholder' })),
    ].slice(0, 3);
  }, [models]);
  return (
    <main className="home">
      <button className="hero-arrow left" aria-label="Previous">‹</button>
      <button className="hero-arrow right" aria-label="Next">›</button>
      <section className="hero-split">
        {heroSlides.map((slide, index) => (
          slide.type === 'model' ? (
            <ModelTile
              key={slide.model.id}
              model={slide.model}
              models={models}
              onOpen={onOpenModel}
              imageUrl={slide.model.featured_image_url || slide.model.headshot_url}
              variant="carousel"
            />
          ) : (
            <CarouselPlaceholder key={`placeholder-${index}`} index={index} />
          )
        ))}
      </section>
    </main>
  );
}

function ageNum(age) {
  const match = (age || '').match(/(\d0)s/i);
  return match ? parseInt(match[1], 10) : 99;
}

function splitTags(value) {
  return String(value || '')
    .split(',')
    .map((item) => item.trim())
    .filter(Boolean);
}

function modelMatchesFilters(model, filters) {
  return ROSTER_FILTERS.every(({ key }) => {
    const selected = filters[key] || [];
    if (!selected.length) return true;
    const modelTags = splitTags(model[key]);
    return selected.some((tag) => modelTags.includes(tag));
  });
}

function Roster({ category, models, onOpenModel, favourites, onToggleFavourite, isAdmin }) {
  const [activeFilter, setActiveFilter] = useState('availability_status');
  const [filters, setFilters] = useState({});
  const [shortlistOnly, setShortlistOnly] = useState(false);
  const list = useMemo(() => {
    return models
      .filter((m) => m.gender === category)
      .filter((model) => isAdmin || model.headshot_url)
      .filter((model) => !shortlistOnly || favourites.includes(model.id))
      .filter((model) => modelMatchesFilters(model, filters))
      .sort((a, b) => a.name.localeCompare(b.name));
  }, [category, favourites, filters, isAdmin, models, shortlistOnly]);

  const selectedFilter = ROSTER_FILTERS.find((filter) => filter.key === activeFilter) || ROSTER_FILTERS[0];
  const selectedCount = Object.values(filters).reduce((total, values = []) => total + values.length, 0) + (shortlistOnly ? 1 : 0);
  const toggleFilterTag = (key, tag) => {
    setFilters((current) => {
      const values = current[key] || [];
      const nextValues = values.includes(tag)
        ? values.filter((value) => value !== tag)
        : [...values, tag];
      return { ...current, [key]: nextValues };
    });
  };

  return (
    <main className="roster">
      <div className="roster-filter-panel">
        <div className="filter-headers">
          <span className="sort-label">FILTER:</span>
          {ROSTER_FILTERS.map((filter) => (
            <button
              key={filter.key}
              className={'sort-val' + (activeFilter === filter.key ? ' on' : '')}
              onClick={() => setActiveFilter(filter.key)}>
              {filter.label}
              {!!(filters[filter.key] || []).length && <span className="filter-count">{filters[filter.key].length}</span>}
            </button>
          ))}
          {!!selectedCount && (
            <button
              className="clear-filters"
              type="button"
              onClick={() => {
                setFilters({});
                setShortlistOnly(false);
              }}>
              CLEAR
            </button>
          )}
          <button
            className={'roster-shortlist-link' + (shortlistOnly ? ' on' : '')}
            type="button"
            aria-pressed={shortlistOnly}
            aria-label={`Filter shortlist${favourites.length ? `, ${favourites.length} models` : ''}`}
            onClick={() => setShortlistOnly((value) => !value)}>
            {shortlistOnly ? '♥' : '♡'} {favourites.length ? `(${favourites.length})` : ''}
          </button>
        </div>
        <div className={`roster-filter-tags filter-${selectedFilter.key}`}>
          {selectedFilter.options.map((tag) => {
            const active = (filters[selectedFilter.key] || []).includes(tag);
            return (
              <button
                key={tag}
                type="button"
                className={'tag-option' + (active ? ' is-selected' : '')}
                aria-pressed={active}
                onClick={() => toggleFilterTag(selectedFilter.key, tag)}>
                {tag}
              </button>
            );
          })}
        </div>
      </div>
      <section className="roster-grid">
        {list.map((model) => (
          <ModelTile
            key={model.id}
            model={model}
            models={models}
            onOpen={onOpenModel}
            isFavourite={favourites.includes(model.id)}
            onToggleFavourite={onToggleFavourite}
            showFavourite={true}
            isRoster={true}
          />
        ))}
      </section>
    </main>
  );
}

function Talent({ handle, models, onOpenModel }) {
  const model = models.find((m) => m.handle === handle);
  if (!model) {
    return (
      <main className="talent">
        <div className="talent-empty">
          <p>NOT FOUND.</p>
          <a href="#/roster/women">BACK TO ROSTER</a>
        </div>
      </main>
    );
  }
  const media = (model.media || []).filter((item) => item.type === 'headshot' || item.type === 'folio');
  const selected = media.find((item) => item.type === 'folio') || media[0];
  return (
    <main className="talent">
      <div className="talent-bar">
        <a className="back" href={`#/roster/${model.gender}`}>BACK</a>
        <h1 className="talent-name">{model.name.toUpperCase()}</h1>
        <div className="talent-actions">
          <button className="talent-action" onClick={() => onOpenModel(model)}>EDIT CARD</button>
        </div>
      </div>
      <div className="talent-stats">
        <span className="stat-handle">@{model.handle}</span>
        <div className="stats-row">
          <Stat label="AGE" value={model.age_group} />
          <Stat label="ETHNICITY" value={model.ethnicity} />
          <Stat label="BUILD" value={model.build} />
          <Stat label="HEIGHT" value={model.height} />
          <Stat label="STYLE" value={model.style} />
        </div>
        <span className="stat-share">CARD</span>
      </div>
      <section className="talent-images">
        <div className="talent-img">
          {selected ? <img className="talent-photo" src={selected.url} alt={model.name} /> : <Placeholder model={model} />}
          <div className="talent-img-overlay">
            <span className="talent-img-label">{model.first.toUpperCase()}</span>
            <span className="talent-img-no">01 / {String(Math.max(media.length, 1)).padStart(2, '0')}</span>
          </div>
        </div>
      </section>
    </main>
  );
}

function Shortlist({ models, favourites, onOpenModel, onToggleFavourite }) {
  const shortlisted = models.filter((model) => favourites.includes(model.id));
  return (
    <main className="roster shortlist-page">
      <div className="roster-controls">
        <div className="sort">
          <span className="sort-label">SHORTLIST:</span>
          <span className="view-val on">{shortlisted.length} MODEL{shortlisted.length === 1 ? '' : 'S'}</span>
        </div>
      </div>
      {shortlisted.length ? (
        <section className="roster-grid compare-grid">
          {shortlisted.map((model) => (
            <ModelTile
              key={model.id}
              model={model}
              models={models}
              onOpen={onOpenModel}
              isFavourite={true}
              onToggleFavourite={onToggleFavourite}
            />
          ))}
        </section>
      ) : (
        <div className="talent-empty">
          <p>NO MODELS SHORTLISTED YET.</p>
          <a href="#/roster/women">BROWSE MODELS</a>
        </div>
      )}
    </main>
  );
}

function Stat({ label, value }) {
  return (
    <span className="stat">
      <span className="stat-l">{label}</span>
      <span className="stat-v">{value || '—'}</span>
    </span>
  );
}

function Contact() {
  return (
    <main className="contact">
      <div className="contact-l">
        <p className="contact-eyebrow">- ENQUIRIES</p>
        <h2 className="contact-h">
          Virtual talent for<br />
          luxury property,<br />
          development &amp; lifestyle.
        </h2>
        <div className="contact-meta">
          <div><span className="cm-l">BOOKINGS</span><span className="cm-v">bookings@ensemble.studio</span></div>
          <div><span className="cm-l">LICENSING</span><span className="cm-v">licensing@ensemble.studio</span></div>
          <div><span className="cm-l">STUDIO</span><span className="cm-v">Los Angeles · Dubai</span></div>
        </div>
      </div>
      <form className="contact-r" onSubmit={(e) => e.preventDefault()}>
        <label><span>NAME</span><input type="text" /></label>
        <label><span>STUDIO / DEVELOPER</span><input type="text" /></label>
        <label><span>EMAIL</span><input type="email" /></label>
        <label><span>PROJECT</span><textarea rows={5}></textarea></label>
        <button type="submit">SEND ENQUIRY</button>
      </form>
    </main>
  );
}

function PublicModelModal({ model, onClose }) {
  const [mediaIndex, setMediaIndex] = useState(0);

  useEffect(() => {
    setMediaIndex(0);
  }, [model]);

  useEffect(() => {
    if (!model) return;
    const onKey = (event) => { if (event.key === 'Escape') onClose(); };
    window.addEventListener('keydown', onKey);
    document.documentElement.style.overflow = 'hidden';
    return () => {
      window.removeEventListener('keydown', onKey);
      document.documentElement.style.overflow = '';
    };
  }, [model, onClose]);

  if (!model) return null;

  const media = (model.media || []).filter((item) => ['featured', 'folio', 'video'].includes(item.type));
  const current = media[mediaIndex] || null;
  const hasMedia = media.length > 0;
  const next = () => setMediaIndex((index) => (index + 1) % media.length);
  const prev = () => setMediaIndex((index) => (index - 1 + media.length) % media.length);

  return (
    <div className="modal-backdrop" role="dialog" aria-modal="true" aria-label={`${model.name} profile`}>
      <div className="model-modal public-model-modal">
        <div className="modal-head">
          <div>
            <span className="modal-kicker">MODEL PROFILE</span>
            <h2>{model.name}</h2>
          </div>
          <button className="modal-close" onClick={onClose} aria-label="Close profile">×</button>
        </div>

        <div className="public-model-grid">
          <div className="stats-row public-stats">
            <Stat label="AGE" value={model.age_group} />
            <Stat label="ETHNICITY" value={model.ethnicity} />
            <Stat label="BUILD" value={model.build} />
            <Stat label="HEIGHT" value={model.height} />
            <Stat label="GENDER" value={model.gender} />
            <Stat label="STYLE" value={model.style} />
          </div>

          <section className="public-gallery">
            <div className="public-media-frame">
              {hasMedia && current.type === 'video' && <video src={current.url} controls></video>}
              {hasMedia && current.type !== 'video' && <img src={current.url} alt={`${model.name} ${current.type}`} />}
              {!hasMedia && <Placeholder model={model} />}
              {media.length > 1 && (
                <>
                  <button className="public-gallery-arrow left" type="button" onClick={prev} aria-label="Previous media">‹</button>
                  <button className="public-gallery-arrow right" type="button" onClick={next} aria-label="Next media">›</button>
                </>
              )}
            </div>
            {media.length > 1 && (
              <div className="public-thumbs">
                {media.map((item, index) => (
                  <button
                    key={item.id}
                    type="button"
                    className={'public-thumb' + (index === mediaIndex ? ' is-selected' : '')}
                    onClick={() => setMediaIndex(index)}>
                    {item.type === 'video' ? <span>VIDEO</span> : <img src={item.url} alt="" />}
                  </button>
                ))}
              </div>
            )}
          </section>

          {model.bio && <p className="public-bio">{model.bio}</p>}
        </div>
      </div>
    </div>
  );
}

function ModelDetailsModal({ model, onClose, onSaved }) {
  const [form, setForm] = useState(model || null);
  const [saving, setSaving] = useState(false);
  const [error, setError] = useState('');

  useEffect(() => {
    setForm(model);
    setError('');
  }, [model]);

  useEffect(() => {
    if (!model) return;
    const onKey = (event) => { if (event.key === 'Escape') onClose(); };
    window.addEventListener('keydown', onKey);
    document.documentElement.style.overflow = 'hidden';
    return () => {
      window.removeEventListener('keydown', onKey);
      document.documentElement.style.overflow = '';
    };
  }, [model, onClose]);

  if (!model) return null;

  const current = form || model;
  const media = current.media || [];
  const headshots = media.filter((item) => item.type === 'headshot');
  const featuredImage = media.find((item) => item.type === 'featured');
  const folio = media.filter((item) => item.type === 'folio');
  const videos = media.filter((item) => item.type === 'video');
  const hoverHeadshot = headshots.length > 1 ? headshots[headshots.length - 1] : null;

  const setField = (field, value) => {
    setForm((currentForm) => ({ ...model, ...currentForm, [field]: value }));
  };

  async function saveDetails(event) {
    event.preventDefault();
    setSaving(true);
    setError('');
    try {
      const updated = await api(`/api/models/${model.id}`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          name: current.name,
          age_group: cleanTagValue(current.age_group, MODEL_TAGS.age_group),
          ethnicity: cleanTagValue(current.ethnicity, MODEL_TAGS.ethnicity),
          build: cleanTagValue(current.build, MODEL_TAGS.build),
          height: cleanTagValue(current.height, MODEL_TAGS.height),
          gender: current.gender,
          style: cleanTagValue(current.style, MODEL_TAGS.style),
          bio: current.bio,
          featured: !!current.featured,
          availability_status: current.availability_status || 'available',
        }),
      });
      setForm(updated);
      onSaved(updated);
    } catch (err) {
      setError(err.message);
    } finally {
      setSaving(false);
    }
  }

  async function uploadMedia(type, files) {
    if (!files || !files.length) return;
    setSaving(true);
    setError('');
    try {
      const body = new FormData();
      body.append('type', type);
      Array.from(files).forEach((file) => body.append('media', file));
      await api(`/api/models/${model.id}/media`, { method: 'POST', body });
      const updated = await api(`/api/models/${model.id}`);
      setForm(updated);
      onSaved(updated);
    } catch (err) {
      setError(err.message);
    } finally {
      setSaving(false);
    }
  }

  async function removeMedia(mediaId) {
    setSaving(true);
    setError('');
    try {
      await api(`/api/media/${mediaId}`, { method: 'DELETE' });
      const updated = await api(`/api/models/${model.id}`);
      setForm(updated);
      onSaved(updated);
    } catch (err) {
      setError(err.message);
    } finally {
      setSaving(false);
    }
  }

  async function toggleFeatured(featured) {
    const previous = current;
    setForm((currentForm) => ({ ...model, ...currentForm, featured }));
    setSaving(true);
    setError('');
    try {
      const updated = await api(`/api/models/${model.id}`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ featured }),
      });
      setForm(updated);
      onSaved(updated);
    } catch (err) {
      setForm(previous);
      setError(err.message);
    } finally {
      setSaving(false);
    }
  }

  return (
    <div className="modal-backdrop" role="dialog" aria-modal="true" aria-label={`${current.name} details`}>
      <div className="model-modal">
        <div className="modal-head">
          <div>
            <span className="modal-kicker">MODEL DETAILS</span>
            <h2>{current.name}</h2>
          </div>
          <button className="modal-close" onClick={onClose} aria-label="Close details">×</button>
        </div>

        <div className="modal-grid">
          <section className="media-panel">
            <Dropzone
              className="headshot-box headshots-box"
              accept="image/*"
              multiple
              label=""
              onFiles={(files) => uploadMedia('headshot', files)}>
              {headshots.length ? (
                <div className="headshot-stack">
                  {headshots.map((item, index) => (
                    <figure key={item.id} className="headshot-item">
                      <img src={item.url} alt={`${current.name} headshot ${index + 1}`} />
                      <span>{item.id === hoverHeadshot?.id ? 'Hover' : index === 0 ? 'Base' : 'Alt'}</span>
                      <button
                        type="button"
                        aria-label={`Remove ${current.name} headshot ${index + 1}`}
                        onClick={(event) => {
                          event.preventDefault();
                          event.stopPropagation();
                          removeMedia(item.id);
                        }}>
                        ×
                      </button>
                    </figure>
                  ))}
                </div>
              ) : (
                <Placeholder model={current} />
              )}
            </Dropzone>
            <Dropzone
              className="headshot-box featured-box"
              accept="image/*"
              label={featuredImage ? 'Replace featured image' : 'Drop featured image'}
              onFiles={(files) => uploadMedia('featured', files)}>
              {featuredImage ? <img src={featuredImage.url} alt={`${current.name} featured`} /> : <Placeholder model={current} />}
            </Dropzone>
            <MediaList
              title="Folio Photos"
              items={folio}
              type="image"
              accept="image/*"
              dropLabel="Drop folio photos"
              onRemove={removeMedia}
              onUpload={(files) => uploadMedia('folio', files)}
            />
            <MediaList
              title="Videos"
              items={videos}
              type="video"
              accept="video/*"
              dropLabel="Drop videos"
              onRemove={removeMedia}
              onUpload={(files) => uploadMedia('video', files)}
            />
          </section>

          <form className="details-form" onSubmit={saveDetails}>
            <label>
              <span>Status</span>
              <select value={current.availability_status || 'available'} onChange={(e) => setField('availability_status', e.target.value)}>
                {AVAILABILITY_STATUSES.map((status) => (
                  <option key={status} value={status}>{status}</option>
                ))}
              </select>
            </label>
            <label className="toggle-field">
              <span>Featured on Homepage</span>
              <input
                type="checkbox"
                checked={!!current.featured}
                onChange={(e) => toggleFeatured(e.target.checked)}
              />
              <span className="toggle-slider" aria-hidden="true"></span>
            </label>
            <Field label="Name" value={current.name} onChange={(v) => setField('name', v)} />
            <TagField label="Age Group" value={current.age_group} options={MODEL_TAGS.age_group} onChange={(v) => setField('age_group', v)} />
            <TagField label="Ethnicity" value={current.ethnicity} options={MODEL_TAGS.ethnicity} onChange={(v) => setField('ethnicity', v)} />
            <TagField label="Build" value={current.build} options={MODEL_TAGS.build} onChange={(v) => setField('build', v)} />
            <TagField label="Height" value={current.height} options={MODEL_TAGS.height} onChange={(v) => setField('height', v)} />
            <label>
              <span>Gender</span>
              <select value={current.gender || ''} onChange={(e) => setField('gender', e.target.value)}>
                <option value="women">women</option>
                <option value="men">men</option>
              </select>
            </label>
            <TagField label="Style" value={current.style} options={MODEL_TAGS.style} onChange={(v) => setField('style', v)} />
            <label>
              <span>Bio</span>
              <textarea rows={5} value={current.bio || ''} onChange={(e) => setField('bio', e.target.value)}></textarea>
            </label>
            {error && <p className="modal-error">{error}</p>}
            <button className="save-btn" type="submit" disabled={saving}>{saving ? 'SAVING...' : 'SAVE DETAILS'}</button>
          </form>
        </div>
      </div>
    </div>
  );
}

function Field({ label, value, onChange }) {
  return (
    <label>
      <span>{label}</span>
      <input type="text" value={value || ''} onChange={(e) => onChange(e.target.value)} />
    </label>
  );
}

function TagField({ label, value, options, onChange }) {
  const selected = String(value || '')
    .split(',')
    .map((item) => item.trim())
    .filter(Boolean);
  const fieldClass = label === 'Age Group' || label === 'Height' ? ' tag-field-fixed-width' : '';

  const toggleOption = (option) => {
    const next = selected.includes(option)
      ? selected.filter((item) => item !== option)
      : [...selected, option];
    onChange(next.join(', '));
  };

  return (
    <div className={`tag-field${fieldClass}`}>
      <span>{label}</span>
      <div className="tag-options">
        {options.map((option) => (
          <button
            key={option}
            type="button"
            aria-pressed={selected.includes(option)}
            className={'tag-option' + (selected.includes(option) ? ' is-selected' : '')}
            onClick={() => toggleOption(option)}>
            {option}
          </button>
        ))}
      </div>
    </div>
  );
}

function Dropzone({ className = '', label, accept, multiple, onFiles, children }) {
  const handleDrop = (event) => {
    event.preventDefault();
    onFiles(event.dataTransfer.files);
  };

  return (
    <label
      className={`dropzone ${className}`}
      onDragOver={(event) => event.preventDefault()}
      onDrop={handleDrop}>
      {children}
      <span className="dropzone-prompt">{label}</span>
      <input
        type="file"
        accept={accept}
        multiple={!!multiple}
        onChange={(event) => {
          onFiles(event.target.files);
          event.target.value = '';
        }}
      />
    </label>
  );
}

function MediaList({ title, items, type, accept, dropLabel, onRemove, onUpload }) {
  return (
    <div className="media-list">
      <h3>{title}</h3>
      <div className="media-grid">
        {items.map((item) => (
          <figure key={item.id} className="media-item">
            {type === 'video'
              ? <video src={item.url} controls></video>
              : <img src={item.url} alt={item.original_name || title} />}
            <button type="button" aria-label={`Remove ${item.original_name || title}`} onClick={() => onRemove(item.id)}>×</button>
          </figure>
        ))}
        <Dropzone
          className="media-item media-dropzone"
          accept={accept}
          multiple
          label={dropLabel}
          onFiles={onUpload}>
          <span className="media-dropzone-plus">+</span>
        </Dropzone>
      </div>
    </div>
  );
}

function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const route = useRoute();
  const pair = TYPE_PAIRS[t.typePair] || TYPE_PAIRS.cormorant;
  const [menuOpen, setMenuOpen] = useState(false);
  const [selectedModel, setSelectedModel] = useState(null);
  const { models, loading, error, replaceModel } = useModels();
  const shortlist = useShortlist();
  const auth = useAdminLogin();
  const isAdmin = !!auth.user;

  useEffect(() => { setMenuOpen(false); }, [route.page, route.category, route.handle]);

  useEffect(() => {
    const root = document.documentElement;
    root.style.setProperty('--bg', t.bgTone);
    root.style.setProperty('--accent', t.accent);
    root.style.setProperty('--display-font', pair.display);
    root.style.setProperty('--body-font', pair.body);
    root.style.setProperty('--display-weight', pair.displayWeight);
    root.style.setProperty('--body-weight', pair.bodyWeight);
    root.style.setProperty('--tracking', pair.tracking);
    root.style.setProperty('--grid-cols', t.gridCols);
  }, [t, pair]);

  const openModel = useCallback((model) => setSelectedModel(model), []);
  const updateSelected = useCallback((updated) => {
    replaceModel(updated);
    setSelectedModel(updated);
  }, [replaceModel]);

  let page = null;
  if (loading) page = <main className="talent"><div className="talent-empty"><p>LOADING ROSTER...</p></div></main>;
  else if (error) page = <main className="talent"><div className="talent-empty"><p>{error}</p></div></main>;
  else if (route.page === 'home') page = <Home models={models} onOpenModel={openModel} favourites={shortlist.ids} onToggleFavourite={shortlist.toggle} />;
  else if (route.page === 'roster') page = <Roster category={route.category} models={models} onOpenModel={openModel} favourites={shortlist.ids} onToggleFavourite={shortlist.toggle} isAdmin={isAdmin} />;
  else if (route.page === 'talent') page = <Talent handle={route.handle} models={models} onOpenModel={openModel} />;
  else if (route.page === 'shortlist') page = <Shortlist models={models} favourites={shortlist.ids} onOpenModel={openModel} onToggleFavourite={shortlist.toggle} />;
  else if (route.page === 'contact') page = <Contact />;

  return (
    <div className="app">
      <TopNav
        brand={t.brand}
        route={route}
        menuOpen={menuOpen}
        onMenuToggle={() => setMenuOpen((v) => !v)}
      />
      {page}
      <Footer />
      <MenuOverlay open={menuOpen} onClose={() => setMenuOpen(false)} brand={t.brand} route={route} auth={auth} />
      {isAdmin ? (
        <ModelDetailsModal model={selectedModel} onClose={() => setSelectedModel(null)} onSaved={updateSelected} />
      ) : (
        <PublicModelModal model={selectedModel} onClose={() => setSelectedModel(null)} />
      )}

      {isAdmin && (
        <TweaksPanel>
          <TweakSection label="Brand" />
          <TweakText label="Name" value={t.brand} onChange={(v) => setTweak('brand', v || 'ENSEMBLE')} />
          <TweakSection label="Typography" />
          <TweakSelect label="Pairing" value={t.typePair}
            options={Object.keys(TYPE_PAIRS).map((k) => ({ value: k, label: TYPE_PAIRS[k].label }))}
            onChange={(v) => setTweak('typePair', v)} />
          <TweakSection label="Palette" />
          <TweakColor label="Background" value={t.bgTone} options={BG_TONES} onChange={(v) => setTweak('bgTone', v)} />
          <TweakColor label="Accent" value={t.accent} options={ACCENTS} onChange={(v) => setTweak('accent', v)} />
          <TweakSection label="Layout" />
          <TweakRadio label="Grid" value={String(t.gridCols)} options={['3', '4', '6']} onChange={(v) => setTweak('gridCols', parseInt(v, 10))} />
        </TweaksPanel>
      )}
    </div>
  );
}

ReactDOM.createRoot(document.getElementById('root')).render(<App />);
