// Admin content editor

const ADMIN_TABS = [
  ['site', 'Site'],
  ['media', 'Media'],
  ['home', 'Home'],
  ['story', 'Story'],
  ['schedule', 'Schedule'],
  ['rsvp', 'RSVP'],
  ['registry', 'Registry'],
  ['gallery', 'Gallery'],
  ['footer', 'Footer'],
];

function PageAdmin({ content, onContentChange, go }) {
  const [session, setSession] = React.useState(null);
  const [draft, setDraft] = React.useState(() => window.cloneJson(content));
  const [active, setActive] = React.useState('home');
  const [status, setStatus] = React.useState('');
  const [busy, setBusy] = React.useState(false);

  React.useEffect(() => {
    let alive = true;
    fetch('/api/admin/session', { credentials: 'same-origin', cache: 'no-store' })
      .then((res) => res.ok ? res.json() : Promise.reject(new Error('Admin API unavailable')))
      .then((data) => {
        if (!alive) return;
        setSession(data);
        if (data.authenticated) loadContent();
      })
      .catch((err) => {
        if (!alive) return;
        setSession({ authenticated: false, apiUnavailable: true });
        setStatus(err.message);
      });
    return () => { alive = false; };
  }, []);

  const loadContent = React.useCallback(async () => {
    setBusy(true);
    setStatus('');
    try {
      const res = await fetch('/api/admin/content', { credentials: 'same-origin', cache: 'no-store' });
      const data = await readApiResponse(res);
      setDraft(window.cloneJson(data));
      onContentChange(data);
      setStatus('Loaded latest content.');
    } catch (err) {
      setStatus(err.message);
    } finally {
      setBusy(false);
    }
  }, [onContentChange]);

  const saveContent = async () => {
    setBusy(true);
    setStatus('');
    try {
      const res = await fetch('/api/admin/content', {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'same-origin',
        body: JSON.stringify(draft),
      });
      const data = await readApiResponse(res);
      const saved = data.content || draft;
      onContentChange(saved);
      setDraft(window.cloneJson(saved));
      setStatus(`Saved at ${new Date().toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' })}.`);
    } catch (err) {
      setStatus(err.message);
    } finally {
      setBusy(false);
    }
  };

  const logout = async () => {
    setBusy(true);
    try {
      await fetch('/api/admin/logout', { method: 'POST', credentials: 'same-origin' });
    } finally {
      setSession({ authenticated: false });
      setBusy(false);
    }
  };

  const updatePath = React.useCallback((path, value) => {
    setDraft((current) => setIn(current, path, value));
  }, []);

  const appendPath = React.useCallback((path) => {
    setDraft((current) => {
      const arr = getIn(current, path);
      const sample = Array.isArray(arr) && arr.length ? arr[arr.length - 1] : '';
      return setIn(current, path, [...(arr || []), emptyLike(sample)]);
    });
  }, []);

  const removePath = React.useCallback((path) => {
    setDraft((current) => {
      const parent = getIn(current, path.slice(0, -1));
      if (!Array.isArray(parent)) return current;
      const index = path[path.length - 1];
      return setIn(current, path.slice(0, -1), parent.filter((_, i) => i !== index));
    });
  }, []);

  const uploadImage = React.useCallback(async (file) => {
    const dataUrl = await readFileAsDataUrl(file);
    const res = await fetch('/api/admin/upload', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      credentials: 'same-origin',
      body: JSON.stringify({ filename: file.name, dataUrl }),
    });
    const upload = await readApiResponse(res);
    setDraft((current) => setIn(current, ['media', 'photos', upload.key], upload.url));
    setStatus(`Uploaded ${file.name}.`);
    return upload;
  }, []);

  if (session === null) {
    return <AdminFrame go={go}><div className="admin-loading">Checking admin session.</div></AdminFrame>;
  }

  if (!session.authenticated) {
    return (
      <AdminFrame go={go}>
        <AdminLogin
          apiUnavailable={session.apiUnavailable}
          status={status}
          onLogin={(data) => {
            setSession(data);
            loadContent();
          }}
        />
      </AdminFrame>
    );
  }

  const dirty = JSON.stringify(draft) !== JSON.stringify(content);

  return (
    <AdminFrame go={go}>
      <div className="admin-toolbar">
        <div>
          <span className="eyebrow">— Admin</span>
          <h1 className="h2" style={{ marginTop: 12 }}>Content<br /><i>management.</i></h1>
        </div>
        <div className="admin-actions">
          <button className="btn" type="button" onClick={loadContent} disabled={busy}>Reload</button>
          <button className="btn fern" type="button" onClick={saveContent} disabled={busy || !dirty}>Save changes <span className="arrow">→</span></button>
          <button className="btn" type="button" onClick={logout} disabled={busy}>Log out</button>
        </div>
      </div>

      <div className="admin-layout">
        <aside className="admin-tabs" aria-label="Content sections">
          {ADMIN_TABS.map(([key, label]) => (
            <button
              type="button"
              key={key}
              className={active === key ? 'active' : ''}
              onClick={() => setActive(key)}
            >
              {label}
            </button>
          ))}
        </aside>

        <section className="admin-editor">
          <div className="admin-editor-head">
            <div>
              <span className="label">Editing</span>
              <h2 className="h3">{ADMIN_TABS.find(([key]) => key === active)?.[1]}</h2>
            </div>
            {dirty && <span className="admin-dirty">Unsaved</span>}
          </div>
          <AdminValue
            value={draft[active]}
            path={[active]}
            label={active}
            onChange={updatePath}
            onAppend={appendPath}
            onRemove={removePath}
            onUpload={uploadImage}
            draft={draft}
          />
          {status && <div className="admin-status">{status}</div>}
        </section>
      </div>
    </AdminFrame>
  );
}

function AdminFrame({ go, children }) {
  return (
    <main className="page-enter admin-page">
      <section className="container">
        <div className="admin-topline">
          <button type="button" className="text-link" onClick={() => go('home')}>Back to site</button>
        </div>
        {children}
      </section>
    </main>
  );
}

function AdminLogin({ onLogin, status, apiUnavailable }) {
  const [username, setUsername] = React.useState('admin');
  const [password, setPassword] = React.useState('');
  const [message, setMessage] = React.useState(status || '');
  const [busy, setBusy] = React.useState(false);

  React.useEffect(() => {
    setMessage(status || '');
  }, [status]);

  const submit = async (event) => {
    event.preventDefault();
    setBusy(true);
    setMessage('');
    try {
      const res = await fetch('/api/admin/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'same-origin',
        body: JSON.stringify({ username, password }),
      });
      const data = await readApiResponse(res);
      onLogin(data);
    } catch (err) {
      setMessage(err.message);
    } finally {
      setBusy(false);
    }
  };

  return (
    <div className="admin-login">
      <span className="eyebrow">— Admin</span>
      <h1 className="h2" style={{ marginTop: 18 }}>Sign in<br /><i>to edit.</i></h1>
      <form className="admin-login-form" onSubmit={submit}>
        <label className="admin-field">
          <span>Username</span>
          <input value={username} onChange={(event) => setUsername(event.target.value)} autoComplete="username" disabled={apiUnavailable} />
        </label>
        <label className="admin-field">
          <span>Password</span>
          <input type="password" value={password} onChange={(event) => setPassword(event.target.value)} autoComplete="current-password" disabled={apiUnavailable} />
        </label>
        <button className="btn fern" type="submit" disabled={busy || apiUnavailable}>Log in <span className="arrow">→</span></button>
      </form>
      {message && <div className="admin-status">{message}</div>}
    </div>
  );
}

function AdminValue({ value, path, label, onChange, onAppend, onRemove, onUpload, draft, depth = 0 }) {
  if (path.length === 1 && path[0] === 'media') {
    return (
      <AdminMediaLibrary
        photos={value.photos || {}}
        onChange={onChange}
        onUpload={onUpload}
      />
    );
  }

  if (Array.isArray(value)) {
    return (
      <div className="admin-array">
        <div className="admin-array-head">
          <span>{prettyLabel(label)}</span>
          <button type="button" className="admin-mini-btn" onClick={() => onAppend(path)}>Add</button>
        </div>
        <div className="admin-array-list">
          {value.map((item, index) => (
            <div className="admin-array-item" key={index}>
              <div className="admin-array-item-head">
                <span>{prettyLabel(label)} {index + 1}</span>
                <button type="button" className="admin-mini-btn danger" onClick={() => onRemove([...path, index])}>Remove</button>
              </div>
              <AdminValue
                value={item}
                path={[...path, index]}
                label={`${label} ${index + 1}`}
                onChange={onChange}
                onAppend={onAppend}
                onRemove={onRemove}
                onUpload={onUpload}
                draft={draft}
                depth={depth + 1}
              />
            </div>
          ))}
        </div>
      </div>
    );
  }

  if (value && typeof value === 'object') {
    return (
      <div className={depth === 0 ? 'admin-fields' : 'admin-fields nested'}>
        {Object.entries(value).map(([key, child]) => (
          <AdminValue
            key={key}
            value={child}
            path={[...path, key]}
            label={key}
            onChange={onChange}
            onAppend={onAppend}
            onRemove={onRemove}
            onUpload={onUpload}
            draft={draft}
            depth={depth + 1}
          />
        ))}
      </div>
    );
  }

  if (typeof value === 'boolean') {
    return (
      <label className="admin-check">
        <input
          type="checkbox"
          checked={value}
          onChange={(event) => onChange(path, event.target.checked)}
        />
        <span>{prettyLabel(label)}</span>
      </label>
    );
  }

  if (typeof value === 'number') {
    return (
      <label className="admin-field">
        <span>{prettyLabel(label)}</span>
        <input
          type="number"
          value={value}
          onChange={(event) => onChange(path, Number(event.target.value))}
        />
      </label>
    );
  }

  const stringValue = value == null ? '' : String(value);
  if (isPhotoKeyField(path)) {
    return (
      <AdminImageField
        value={stringValue}
        path={path}
        label={label}
        draft={draft}
        onChange={onChange}
        onUpload={onUpload}
      />
    );
  }
  const long = stringValue.length > 68 || stringValue.includes('\n');
  return (
    <label className="admin-field">
      <span>{prettyLabel(label)}</span>
      {long ? (
        <textarea
          value={stringValue}
          rows={Math.min(8, Math.max(3, stringValue.split('\n').length + 1))}
          onChange={(event) => onChange(path, event.target.value)}
        />
      ) : (
        <input
          value={stringValue}
          onChange={(event) => onChange(path, event.target.value)}
        />
      )}
    </label>
  );
}

function AdminMediaLibrary({ photos, onChange, onUpload }) {
  const entries = Object.entries(photos);
  const addPhoto = () => {
    const key = uniqueMediaKey(photos, 'photo');
    onChange(['media', 'photos', key], '');
  };
  const removePhoto = (key) => {
    const next = { ...photos };
    delete next[key];
    onChange(['media', 'photos'], next);
  };

  return (
    <div className="admin-media">
      <div className="admin-array-head">
        <span>Image Library</span>
        <button type="button" className="admin-mini-btn" onClick={addPhoto}>Add URL</button>
      </div>
      <div className="admin-media-grid">
        {entries.map(([key, url]) => (
          <div className="admin-media-card" key={key}>
            <div className="admin-media-preview" style={{ backgroundImage: previewBackground(url) }} />
            <label className="admin-field">
              <span>{key}</span>
              <input
                value={url}
                placeholder="https://..."
                onChange={(event) => onChange(['media', 'photos', key], event.target.value)}
              />
            </label>
            <div className="admin-media-actions">
              <AdminUploadButton onUpload={async (file) => {
                const upload = await onUpload(file);
                onChange(['media', 'photos'], { ...photos, [key]: upload.url });
              }} />
              <button type="button" className="admin-mini-btn danger" onClick={() => removePhoto(key)}>Remove</button>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

function AdminImageField({ value, path, label, draft, onChange, onUpload }) {
  const [uploading, setUploading] = React.useState(false);
  const photos = draft?.media?.photos || {};
  const options = Object.keys(photos).sort((a, b) => a.localeCompare(b));
  const preview = mediaPreviewUrl(value, draft);

  const uploadAndUse = async (file) => {
    setUploading(true);
    try {
      const upload = await onUpload(file);
      onChange(path, upload.key);
    } finally {
      setUploading(false);
    }
  };

  return (
    <div className="admin-image-field">
      <div className="admin-image-label">{prettyLabel(label)}</div>
      <div className="admin-image-row">
        <div className="admin-image-preview" style={{ backgroundImage: previewBackground(preview) }} />
        <div className="admin-image-controls">
          <select value={value} onChange={(event) => onChange(path, event.target.value)}>
            <option value="">Choose an image</option>
            {options.map((key) => (
              <option value={key} key={key}>{key}</option>
            ))}
          </select>
          <input
            value={value}
            placeholder="photo key or image URL"
            onChange={(event) => onChange(path, event.target.value)}
          />
          <div className="admin-media-actions">
            <AdminUploadButton disabled={uploading} onUpload={uploadAndUse} />
          </div>
        </div>
      </div>
    </div>
  );
}

function AdminUploadButton({ onUpload, disabled }) {
  const inputRef = React.useRef(null);
  return (
    <>
      <button type="button" className="admin-mini-btn" disabled={disabled} onClick={() => inputRef.current?.click()}>
        Upload
      </button>
      <input
        ref={inputRef}
        className="admin-upload-input"
        type="file"
        accept="image/png,image/jpeg,image/webp,image/gif"
        onChange={(event) => {
          const file = event.target.files?.[0];
          event.target.value = '';
          if (file) onUpload(file);
        }}
      />
    </>
  );
}

async function readApiResponse(res) {
  let data;
  try {
    data = await res.json();
  } catch {
    data = {};
  }
  if (!res.ok) throw new Error(data.error || `Request failed (${res.status})`);
  return data;
}

function getIn(value, path) {
  return path.reduce((acc, key) => acc == null ? undefined : acc[key], value);
}

function setIn(value, path, nextValue) {
  if (!path.length) return nextValue;
  const [head, ...rest] = path;
  const copy = Array.isArray(value) ? value.slice() : { ...value };
  copy[head] = setIn(value ? value[head] : undefined, rest, nextValue);
  return copy;
}

function emptyLike(value) {
  if (Array.isArray(value)) return [];
  if (value && typeof value === 'object') {
    return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, emptyLike(child)]));
  }
  if (typeof value === 'boolean') return false;
  if (typeof value === 'number') return 0;
  return '';
}

function isPhotoKeyField(path) {
  const key = String(path[path.length - 1] || '');
  return key === 'photoKey' || key.endsWith('PhotoKey') || key.endsWith('ImageKey');
}

function mediaPreviewUrl(value, draft) {
  if (!value) return '';
  if (/^(https?:)?\/\//.test(value) || value.startsWith('/')) return value;
  const base = draft?.media?.photos?.[value] || window.PHOTOS?.[value] || '';
  if (!base) return '';
  if (base.startsWith('/') || !base.includes('images.unsplash.com')) return base;
  return `${base}?auto=format&fit=crop&w=600&q=80`;
}

function previewBackground(url) {
  return url ? `url("${url}")` : 'none';
}

function readFileAsDataUrl(file) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = () => resolve(reader.result);
    reader.onerror = () => reject(new Error('Could not read image file'));
    reader.readAsDataURL(file);
  });
}

function uniqueMediaKey(photos, prefix) {
  let index = Object.keys(photos).length + 1;
  let key = `${prefix}-${index}`;
  while (Object.prototype.hasOwnProperty.call(photos, key)) {
    index += 1;
    key = `${prefix}-${index}`;
  }
  return key;
}

function prettyLabel(label) {
  const normalized = String(label || '')
    .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
    .replace(/[-_]/g, ' ')
    .replace(/\bcta\b/gi, 'CTA')
    .replace(/\brsvp\b/gi, 'RSVP')
    .replace(/\biso\b/gi, 'ISO');
  return normalized.replace(/\b\w/g, (letter) => letter.toUpperCase());
}

window.PageAdmin = PageAdmin;
