// ════════════════════════════════════════════════════════════
//  Caulis — backend sync (Oracle box, Postgres via caulis-backend)
//  Real username/password identity: the garden's raw `key` IS the
//  server-side identity (bcrypt password_hash column), no client-side
//  hashing into a derived "node" path. gardenNodeId() is kept only as
//  the existing call-site name — it now just ensures a login session
//  and returns the key itself, unchanged. The old sha256(key+password)
//  scheme survives narrowly as a one-time recovery lookup for gardens
//  migrated from the earlier Firebase prototype (see _legacyHash).
// ════════════════════════════════════════════════════════════

const BACKEND_URL = 'https://api.caulis.czeddaru.dev';
const SYNC_READY = true;

// key -> { password, token }
const _sessions = {};
let _activeKey = null;

async function _api(path, opts = {}, token) {
  const headers = { 'content-type': 'application/json', ...(opts.headers || {}) };
  if (token) headers.authorization = `Bearer ${token}`;
  const r = await fetch(`${BACKEND_URL}${path}`, { ...opts, headers });
  let data = null;
  try { data = await r.json(); } catch (e) {}
  return { ok: r.ok, status: r.status, data };
}

// only used to recover a garden migrated from the old Firebase model,
// which is keyed server-side by this same derivation (legacy_node_hash)
async function _legacyHash(key, password) {
  const raw = (key || '') + '::' + (password || '');
  try {
    const buf = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(raw));
    return [...new Uint8Array(buf)].map(b => b.toString(16).padStart(2, '0')).join('');
  } catch (e) { return null; }
}

// login-or-register for (key,password), cache the token under the key.
// Never throws — a wrong password / network failure just leaves no token,
// which every read/write below treats as "no access".
async function _ensureSession(key, password) {
  _sessions[key] = { ..._sessions[key], password };
  const login = await _api('/api/gardens/login', { method: 'POST', body: JSON.stringify({ key, password }) });
  if (login.ok) { _sessions[key].token = login.data.token; return; }
  if (login.status === 404) {
    const legacyHash = await _legacyHash(key, password);
    if (legacyHash) {
      const claim = await _api('/api/gardens/claim', { method: 'POST', body: JSON.stringify({ oldKey: legacyHash, newKey: key, newPassword: password }) });
      if (claim.ok) { _sessions[key].token = claim.data.token; return; }
    }
    const reg = await _api('/api/gardens/register', { method: 'POST', body: JSON.stringify({ key, password }) });
    if (reg.ok) { _sessions[key].token = reg.data.token; return; }
  }
  // wrong password, or register lost a race — leave token unset
}

async function gardenNodeId(key, password) {
  await _ensureSession(key, password);
  return key;
}

// called once from app.jsx's main sync effect so the perenual/AI proxy
// calls (which don't carry a key around) know which session is "current"
function setActiveGarden(key) { _activeKey = key; }
function getActiveToken() { return _activeKey ? _sessions[_activeKey]?.token : null; }

function listenGarden(key, onData) {
  const session = _sessions[key];
  if (!session?.token) return () => {};
  let lastStamp = null;
  let stopped = false;
  const poll = async () => {
    const { ok, data } = await _api('/api/garden', {}, session.token);
    if (ok && data && data.updated_at !== lastStamp) {
      lastStamp = data.updated_at;
      onData({ plants: data.plants, locations: data.locations, queue: data.queue });
    }
  };
  poll();
  const id = setInterval(() => { if (!stopped) poll(); }, 20000);
  return () => { stopped = true; clearInterval(id); };
}

async function pushGarden(key, data) {
  const session = _sessions[key];
  if (!session?.token) return;
  const clean = JSON.parse(JSON.stringify(data, (_, v) => v === undefined ? null : v));
  await _api('/api/garden', { method: 'PUT', body: JSON.stringify(clean) }, session.token);
}

async function fetchGardenOnce(key) {
  const session = _sessions[key];
  if (!session?.token) return null;
  const { ok, data } = await _api('/api/garden', {}, session.token);
  return ok ? { plants: data.plants, locations: data.locations, queue: data.queue } : null;
}

async function gardenExists(key) {
  const { ok, data } = await _api(`/api/gardens/exists?key=${encodeURIComponent(key)}`);
  return ok ? !!data.exists : false;
}

// password-only change: same key, no rename needed, just update the credential
async function changeGardenPassword(key, newPassword) {
  const session = _sessions[key];
  if (!session?.token) return false;
  const { ok } = await _api('/api/gardens/password', { method: 'PATCH', body: JSON.stringify({ newPassword }) }, session.token);
  if (ok) session.password = newPassword;
  return ok;
}

// real key rename: newKey's session already established via gardenNodeId(newKey, pw)
async function renameGarden(oldKey, newKey, data) {
  const oldSession = _sessions[oldKey], newSession = _sessions[newKey];
  if (!newSession?.token) return false;
  await pushGarden(newKey, data);
  if (oldSession?.token) await _api('/api/gardens/me', { method: 'DELETE' }, oldSession.token);
  return true;
}

async function pushPhoto(key, plantId, photos) {
  const session = _sessions[key];
  if (!session?.token) return;
  await _api(`/api/garden/photos/${encodeURIComponent(plantId)}`, { method: 'PUT', body: JSON.stringify({ photos: photos || [] }) }, session.token);
}

async function fetchPhotos(key, plantId) {
  const session = _sessions[key];
  if (!session?.token) return [];
  const { ok, data } = await _api(`/api/garden/photos/${encodeURIComponent(plantId)}`, {}, session.token);
  return ok ? (data.photos || []) : [];
}

async function deletePhotos(key, plantId) {
  const session = _sessions[key];
  if (!session?.token) return;
  await _api(`/api/garden/photos/${encodeURIComponent(plantId)}`, { method: 'DELETE' }, session.token);
}

// admin: cross-garden access via a shared secret (Settings → Developer),
// entirely separate from the per-garden JWT sessions above
async function adminListGardens(secret) {
  const { ok, data } = await _api('/api/admin/gardens', { headers: { 'x-admin-secret': secret } });
  return ok ? (data.gardens || []) : null;
}

async function adminGetGarden(secret, key) {
  const { ok, data } = await _api(`/api/admin/gardens/${encodeURIComponent(key)}`, { headers: { 'x-admin-secret': secret } });
  return ok ? data : null;
}

async function adminPushGarden(secret, key, data) {
  const clean = JSON.parse(JSON.stringify(data, (_, v) => v === undefined ? null : v));
  const { ok } = await _api(`/api/admin/gardens/${encodeURIComponent(key)}`, { method: 'PUT', headers: { 'x-admin-secret': secret }, body: JSON.stringify(clean) });
  return ok;
}

async function adminDeleteGarden(secret, key) {
  const { ok } = await _api(`/api/admin/gardens/${encodeURIComponent(key)}`, { method: 'DELETE', headers: { 'x-admin-secret': secret } });
  return ok;
}

Object.assign(window, {
  listenGarden, pushGarden, gardenExists, renameGarden, fetchGardenOnce, gardenNodeId, changeGardenPassword,
  SYNC_READY, pushPhoto, fetchPhotos, deletePhotos, setActiveGarden, getActiveToken, BACKEND_URL,
  adminListGardens, adminGetGarden, adminPushGarden, adminDeleteGarden,
});
