// Opens the object store that holds converted documents, creating it on first use.
function openStore() {
  return new Promise((resolve, reject) => {
    const req = indexedDB.open('onionskin', 1);
    req.onupgradeneeded = () => {
      const db = req.result;
      if (!db.objectStoreNames.contains('docs')) db.createObjectStore('docs', { keyPath: 'id' });
    };
    req.onsuccess = () => resolve(req.result);
    req.onerror = () => reject(req.error);
  });
}

// Wraps one store transaction and resolves when it commits, so a caller never
// reads back a write the browser has not finished.
function tx(mode, run) {
  return openStore().then((db) => new Promise((resolve, reject) => {
    const t = db.transaction('docs', mode);
    const out = run(t.objectStore('docs'));
    t.oncomplete = () => resolve(out && out.result !== undefined ? out.result : out);
    t.onerror = () => reject(t.error);
    t.onabort = () => reject(t.error);
  }));
}

const Store = {
  all: () => tx('readonly', (s) => s.getAll()),
  put: (doc) => tx('readwrite', (s) => s.put(doc)),
  get: (id) => tx('readonly', (s) => s.get(id)),
  remove: (id) => tx('readwrite', (s) => s.delete(id)),
};

// Formats a byte count at the largest unit that leaves a number under 1000.
function bytes(n) {
  if (!n) return '0 B';
  const u = ['B', 'KB', 'MB', 'GB'];
  let i = 0;
  let v = n;
  while (v >= 1000 && i < u.length - 1) { v /= 1024; i++; }
  return (v >= 100 || i === 0 ? Math.round(v) : v.toFixed(1)) + ' ' + u[i];
}

// Formats a timestamp as a short local date.
function when(iso) {
  try {
    return new Date(iso).toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' });
  } catch (e) { return ''; }
}

function Icon({ name, size = 18 }) {
  const p = {
    file: 'M6 2h7l5 5v15H6zM13 2v5h5',
    import: 'M12 3v11m0 0 4-4m-4 4-4-4M4 17v3h16v-3',
    layers: 'M12 3 3 8l9 5 9-5zM3 14l9 5 9-5',
    trash: 'M4 6h16M9 6V4h6v2m-8 0 1 15h8l1-15',
    download: 'M12 3v12m0 0 4-4m-4 4-4-4M4 19h16',
  }[name] || '';
  return (
    <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor"
      strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <path d={p} />
    </svg>
  );
}

function Btn({ children, kind, icon, ...rest }) {
  const cls = 'btn' + (kind === 'primary' ? ' btn-primary' : kind === 'ghost' ? ' btn-ghost' : '');
  return (
    <button type="button" className={cls} {...rest}
      style={{ display: 'inline-flex', alignItems: 'center', gap: 'var(--s-2)', ...(rest.style || {}) }}>
      {icon && <Icon name={icon} size={15} />}{children}
    </button>
  );
}

function Stat({ value, label, tone }) {
  return (
    <div className="stat">
      <b style={tone ? { color: `var(--${tone})` } : null}>{value}</b>
      <span>{label}</span>
    </div>
  );
}

function Head({ kicker, title, sub, right }) {
  return (
    <div style={{ display: 'flex', alignItems: 'flex-end', gap: 'var(--s-5)', marginBottom: 'var(--s-5)' }}>
      <div style={{ flex: 1, minWidth: 0 }}>
        {kicker && <div className="label" style={{ marginBottom: 6 }}>{kicker}</div>}
        <div className="h1">{title}</div>
        {sub && <div className="sub" style={{ marginTop: 8, maxWidth: '68ch' }}>{sub}</div>}
      </div>
      {right}
    </div>
  );
}

function Toast({ msg }) {
  if (!msg) return null;
  return <div className="toast" role="status">{msg}</div>;
}

// Lists stored documents and the import entry point.
function Rail({ docs, currentId, onOpen, onImport, onDelete }) {
  return (
    <nav className="rail" aria-label="Documents">
      <Btn kind="primary" icon="import" onClick={onImport}
        style={{ width: '100%', justifyContent: 'center', marginBottom: 'var(--s-4)' }}>
        Convert a PDF
      </Btn>
      <div className="label" style={{ padding: '0 var(--s-3) var(--s-2)' }}>
        Documents {docs.length ? `(${docs.length})` : ''}
      </div>
      {!docs.length && (
        <p className="sub" style={{ fontSize: 'var(--fs-300)', padding: '0 var(--s-3)', color: 'var(--muted)' }}>
          Nothing converted yet. Everything you convert stays in this browser.
        </p>
      )}
      {docs.map((d) => (
        <div key={d.id} style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
          <button className="doc-row" aria-current={d.id === currentId} onClick={() => onOpen(d.id)}>
            <span className="name" title={d.title}>{d.title}</span>
            <span className="meta">{d.pages}p</span>
          </button>
          <Btn kind="ghost" icon="trash" aria-label={`Delete ${d.title}`} title="Delete"
            onClick={() => onDelete(d)} style={{ padding: 'var(--s-2)' }} />
        </div>
      ))}
    </nav>
  );
}

Object.assign(window, { Store, bytes, when, Icon, Btn, Stat, Head, Toast, Rail });
