// flow-sync.jsx — Supabase cloud sync for the flow test app
//
// Provides:
//   - useSession() hook for current auth state
//   - <CloudButton /> in the header (sign in / status menu)
//   - uploadTest(entry, profile) / deleteTest(id) / pullAndMerge(setHistory, profile)
//   - ensureProfile(orgName) for first-time onboarding
//
// All saved tests are stored in flow_tests.raw_data (jsonb), with key fields
// also indexed in flat columns so they're queryable from SQL.

const FLOW_SUPABASE_URL = 'https://nndqlijduukqxedljhlr.supabase.co';
const FLOW_SUPABASE_KEY = 'sb_publishable_s01jFkKh0WWsx8qPWANdyg_l_VNegwL';

// The supabase client is loaded asynchronously via <script type="module">
// in index.html, so it may not exist yet when this script runs. Resolve a
// promise once it's available so callers can `await getClient()`.
let __sbClient = null;
const __clientReady = new Promise((resolve) => {
  const tryInit = () => {
    if (window.supabase?.createClient) {
      __sbClient = window.supabase.createClient(FLOW_SUPABASE_URL, FLOW_SUPABASE_KEY, {
        auth: { persistSession: true, autoRefreshToken: true, detectSessionInUrl: true },
      });
      resolve(__sbClient);
      return true;
    }
    return false;
  };
  if (!tryInit()) {
    window.addEventListener('supabase-ready', tryInit, { once: true });
    // Fallback poll in case the event fired before this listener was attached
    const iv = setInterval(() => { if (tryInit()) clearInterval(iv); }, 100);
    setTimeout(() => clearInterval(iv), 10000);
  }
});
async function getClient() { return __sbClient || await __clientReady; }

// ─────────────────────────────────────────────────────────────────────────────
// Auth state — single source of truth, replicated to React via useSession

let __session = null;
const __sessionListeners = new Set();
(async () => {
  const sb = await getClient();
  const { data } = await sb.auth.getSession();
  __session = data.session;
  __sessionListeners.forEach((fn) => fn(__session));
  sb.auth.onAuthStateChange((_event, session) => {
    __session = session;
    __sessionListeners.forEach((fn) => fn(session));
  });
})();

function useSession() {
  const [s, setS] = React.useState(__session);
  React.useEffect(() => {
    __sessionListeners.add(setS);
    return () => __sessionListeners.delete(setS);
  }, []);
  return s;
}

// ─────────────────────────────────────────────────────────────────────────────
// Profile lookup — caches the user's row from `profiles` table (joined with org)

let __profile = null;
const __profileListeners = new Set();

async function loadProfile() {
  if (!__session) { __profile = null; notifyProfile(); return null; }
  const sb = await getClient();
  const { data, error } = await sb
    .from('profiles')
    .select('id, email, full_name, role, organization_id, organizations(name)')
    .eq('id', __session.user.id)
    .maybeSingle();
  if (error) { console.warn('loadProfile error', error); __profile = null; }
  else __profile = data;
  notifyProfile();
  return __profile;
}
function notifyProfile() { __profileListeners.forEach((fn) => fn(__profile)); }

function useProfile() {
  const [p, setP] = React.useState(__profile);
  React.useEffect(() => {
    __profileListeners.add(setP);
    return () => __profileListeners.delete(setP);
  }, []);
  return p;
}

// Refresh profile whenever auth state changes
__sessionListeners.add(() => { loadProfile(); });

// ─────────────────────────────────────────────────────────────────────────────
// Org + profile bootstrap (first-time sign-in)

async function ensureProfile(orgName) {
  if (!__session) throw new Error('Not signed in');
  const sb = await getClient();
  const userId = __session.user.id;
  const email = __session.user.email;

  await loadProfile();
  if (__profile && __profile.organization_id) return __profile;

  const { data: org, error: orgErr } = await sb
    .from('organizations').insert({ name: orgName }).select().single();
  if (orgErr) throw orgErr;

  const { error: pErr } = await sb
    .from('profiles').upsert({
      id: userId, email, organization_id: org.id, role: 'admin',
    });
  if (pErr) throw pErr;

  await loadProfile();
  return __profile;
}

// ─────────────────────────────────────────────────────────────────────────────
// Sync — push / pull / delete

// Coerce a local test entry to a Supabase row
function entryToRow(entry, profile) {
  // Ensure UUID id (browsers >= 2022 ship crypto.randomUUID)
  const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(entry.id || '');
  if (!isUuid) entry.id = crypto.randomUUID();
  return {
    id: entry.id,
    organization_id: profile.organization_id,
    tester_id: profile.id,
    label: entry.label || null,
    ts: new Date(entry.ts || Date.now()).toISOString(),
    target_psi: entry.target ?? null,
    static_psi: numOrNull(entry.staticPsi),
    static_lat: entry.staticLocation?.lat ?? null,
    static_lng: entry.staticLocation?.lng ?? null,
    flow_lat:   entry.flowLocation?.lat   ?? null,
    flow_lng:   entry.flowLocation?.lng   ?? null,
    mode: entry.mode || null,
    test_gpm: numOrNull(entry.testGpm),
    available_gpm: numOrNull(entry.available),
    raw_data: entry,
  };
}
function numOrNull(x) {
  if (x === '' || x == null) return null;
  const n = Number(x);
  return isFinite(n) ? n : null;
}

async function uploadTest(entry, profile) {
  if (!profile?.organization_id) return null;
  const sb = await getClient();
  const row = entryToRow(entry, profile);
  const { error } = await sb.from('flow_tests').upsert(row, { onConflict: 'id' });
  if (error) console.warn('uploadTest', error);
  return entry;
}

async function deleteTest(id) {
  const sb = await getClient();
  const { error } = await sb.from('flow_tests').delete().eq('id', id);
  if (error) console.warn('deleteTest', error);
}

async function pullAndMerge(profile, currentHistory) {
  if (!profile?.organization_id) return currentHistory;
  const sb = await getClient();
  const { data, error } = await sb
    .from('flow_tests').select('id, ts, raw_data')
    .eq('organization_id', profile.organization_id)
    .order('ts', { ascending: false }).limit(500);
  if (error) { console.warn('pullAndMerge', error); return currentHistory; }
  const cloudEntries = (data || []).map((r) => r.raw_data || { id: r.id, ts: r.ts });
  const byId = new Map(currentHistory.map((h) => [h.id, h]));
  cloudEntries.forEach((c) => {
    if (!c.id) return;
    const existing = byId.get(c.id);
    if (!existing || (c.ts || 0) >= (existing.ts || 0)) byId.set(c.id, c);
  });
  return [...byId.values()].sort((a, b) => (b.ts || 0) - (a.ts || 0));
}

async function pushAll(history, profile) {
  if (!profile?.organization_id) return;
  const sb = await getClient();
  const rows = history.map((e) => entryToRow(e, profile));
  if (rows.length === 0) return;
  const { error } = await sb.from('flow_tests').upsert(rows, { onConflict: 'id' });
  if (error) console.warn('pushAll', error);
}

async function signInWithPassword(email, password) {
  const sb = await getClient();
  return sb.auth.signInWithPassword({ email, password });
}
async function signOut() {
  const sb = await getClient();
  return sb.auth.signOut();
}

// ─────────────────────────────────────────────────────────────────────────────
// UI — CloudButton (header) + LoginModal + OnboardingModal

function CloudButton({ onSignedIn }) {
  const session = useSession();
  const profile = useProfile();
  const [open, setOpen] = React.useState(false);
  const [loginOpen, setLoginOpen] = React.useState(false);
  const [onboardOpen, setOnboardOpen] = React.useState(false);
  const ref = React.useRef(null);

  // Close popover on outside click
  React.useEffect(() => {
    if (!open) return;
    const onDown = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', onDown);
    return () => document.removeEventListener('mousedown', onDown);
  }, [open]);

  // After sign-in, ensure profile + org exists; otherwise open onboarding
  React.useEffect(() => {
    if (!session) return;
    (async () => {
      const p = await loadProfile();
      if (!p || !p.organization_id) setOnboardOpen(true);
      else onSignedIn?.(p);
    })();
  }, [session?.user?.id]);

  const signedIn = !!session;
  const provisioned = signedIn && profile?.organization_id;

  return (
    <div ref={ref} style={{ position: 'relative' }}>
      <button onClick={() => signedIn ? setOpen(!open) : setLoginOpen(true)}
        style={cloudStyles.btn}
        aria-label={signedIn ? 'Sync menu' : 'Sign in'}
        title={signedIn ? `Signed in as ${session.user.email}` : 'Sign in to sync'}>
        <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
          <path d="M4.5 11.5h7a2.5 2.5 0 0 0 .5-4.95A3.5 3.5 0 0 0 5.05 6 3 3 0 0 0 4.5 11.5z"
            stroke="currentColor" strokeWidth="1.3" strokeLinejoin="round"
            fill={provisioned ? 'currentColor' : 'none'} fillOpacity={provisioned ? 0.18 : 0} />
        </svg>
        {provisioned && <span style={cloudStyles.dot} />}
      </button>

      {open && signedIn && (
        <div style={cloudStyles.menu}>
          <div style={cloudStyles.menuHd}>
            <div style={cloudStyles.menuUser}>{session.user.email}</div>
            {profile?.organizations?.name && (
              <div style={cloudStyles.menuOrg}>{profile.organizations.name}</div>
            )}
            {profile?.role && (
              <div style={cloudStyles.menuRole}>{profile.role.toUpperCase()}</div>
            )}
          </div>
          <button onClick={() => { setOpen(false); signOut(); }} style={cloudStyles.menuItem}>
            Sign out
          </button>
        </div>
      )}

      {loginOpen && <LoginModal onClose={() => setLoginOpen(false)} />}
      {onboardOpen && profile === null && signedIn === false ? null : null}
      {onboardOpen && signedIn && !provisioned && (
        <OnboardingModal email={session.user.email}
          onClose={() => setOnboardOpen(false)}
          onDone={async (orgName) => {
            try { await ensureProfile(orgName); setOnboardOpen(false); onSignedIn?.(__profile); }
            catch (e) { alert(`Could not finish setup: ${e.message}`); }
          }} />
      )}
    </div>
  );
}

function LoginModal({ onClose }) {
  const [email, setEmail] = React.useState('');
  const [password, setPassword] = React.useState('');
  const [state, setState] = React.useState('idle'); // 'idle' | 'signing' | 'error'
  const [err, setErr] = React.useState('');

  const submit = async (e) => {
    e.preventDefault();
    if (!email.trim() || !password) return;
    setState('signing'); setErr('');
    const { error } = await signInWithPassword(email.trim(), password);
    if (error) {
      setState('error');
      // Common Supabase error messages — surface a clearer hint
      const msg = (error.message || '').toLowerCase();
      if (msg.includes('invalid login credentials')) {
        setErr('Email or password is incorrect.');
      } else if (msg.includes('email not confirmed')) {
        setErr('Account exists but is not confirmed yet. Contact your admin.');
      } else {
        setErr(error.message || 'Could not sign in.');
      }
    } else {
      // Success — close modal; parent CloudButton picks up the session change
      onClose();
    }
  };

  return (
    <>
      <div style={cloudStyles.scrim} onClick={onClose} />
      <div style={cloudStyles.modal}>
        <div style={cloudStyles.modalHd}>
          <div style={cloudStyles.modalTitle}>Sign in to sync</div>
          <button onClick={onClose} style={cloudStyles.modalX} aria-label="Close">
            <svg width="14" height="14" viewBox="0 0 14 14">
              <path d="M3 3l8 8M11 3l-8 8" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round"/>
            </svg>
          </button>
        </div>
        <form onSubmit={submit} style={cloudStyles.modalBody}>
          <div style={{ fontSize: 12.5, color: '#a8a8b2', marginBottom: 10, lineHeight: 1.5 }}>
            Use the email and password your admin sent you. Tests sync across devices
            when signed in.
          </div>
          <input type="email" autoFocus required value={email}
            onChange={(e) => setEmail(e.target.value)}
            placeholder="you@example.com"
            autoComplete="username"
            style={cloudStyles.modalInput} />
          <input type="password" required value={password}
            onChange={(e) => setPassword(e.target.value)}
            placeholder="Password"
            autoComplete="current-password"
            style={{ ...cloudStyles.modalInput, marginTop: 8 }} />
          {state === 'error' && (
            <div style={cloudStyles.modalErr}>{err}</div>
          )}
          <button type="submit" disabled={state === 'signing' || !email.trim() || !password}
            style={{ ...cloudStyles.modalBtn,
                     opacity: state === 'signing' || !email.trim() || !password ? 0.5 : 1 }}>
            {state === 'signing' ? 'Signing in…' : 'Sign in'}
          </button>
          <div style={{ fontSize: 11, color: '#7a7a84', marginTop: 10, lineHeight: 1.5, textAlign: 'center' }}>
            Don't have an account? Contact your admin to be added.
          </div>
        </form>
      </div>
    </>
  );
}

function OnboardingModal({ email, onClose, onDone }) {
  const [orgName, setOrgName] = React.useState('');
  const [busy, setBusy] = React.useState(false);
  const submit = async (e) => {
    e.preventDefault();
    if (!orgName.trim()) return;
    setBusy(true);
    await onDone(orgName.trim());
    setBusy(false);
  };
  return (
    <>
      <div style={cloudStyles.scrim} />
      <div style={cloudStyles.modal}>
        <div style={cloudStyles.modalHd}>
          <div style={cloudStyles.modalTitle}>Set up your organization</div>
        </div>
        <form onSubmit={submit} style={cloudStyles.modalBody}>
          <div style={{ fontSize: 12.5, color: '#a8a8b2', marginBottom: 10, lineHeight: 1.5 }}>
            Welcome, <b style={{ color: '#fafaf7' }}>{email}</b>. What's the name of your
            organization? (Fire dept, water utility, sprinkler contractor, etc.) You'll be
            able to invite teammates later.
          </div>
          <input type="text" autoFocus required value={orgName}
            onChange={(e) => setOrgName(e.target.value)}
            placeholder="e.g. Anytown Fire Department"
            style={cloudStyles.modalInput} />
          <button type="submit" disabled={busy || !orgName.trim()}
            style={{ ...cloudStyles.modalBtn, opacity: busy || !orgName.trim() ? 0.5 : 1 }}>
            {busy ? 'Creating…' : 'Create organization'}
          </button>
        </form>
      </div>
    </>
  );
}

const cloudStyles = {
  btn: { position: 'relative', width: 36, height: 36, borderRadius: 9,
         border: '1px solid #26262e', background: '#15151a', color: '#a8a8b2',
         cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 0 },
  dot: { position: 'absolute', top: 6, right: 6, width: 6, height: 6, borderRadius: '50%',
         background: '#4ade80', boxShadow: '0 0 4px #4ade80' },
  menu: { position: 'absolute', top: 'calc(100% + 6px)', right: 0, width: 220,
          background: '#15151a', border: '1px solid #26262e', borderRadius: 10,
          boxShadow: '0 12px 32px rgba(0,0,0,0.5)', overflow: 'hidden', zIndex: 60 },
  menuHd: { padding: '12px 14px', borderBottom: '1px solid #1f1f25',
            display: 'flex', flexDirection: 'column', gap: 2 },
  menuUser: { fontSize: 12, color: '#fafaf7', fontWeight: 600,
              overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' },
  menuOrg: { fontSize: 11, color: '#a8a8b2' },
  menuRole: { fontSize: 9.5, fontWeight: 700, color: 'var(--accent)',
              letterSpacing: '0.08em', marginTop: 2 },
  menuItem: { width: '100%', padding: '10px 14px', background: 'transparent', border: 0,
              color: '#fafaf7', fontSize: 12.5, fontWeight: 500, textAlign: 'left',
              cursor: 'pointer', fontFamily: 'inherit' },

  scrim: { position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.65)', zIndex: 70,
           backdropFilter: 'blur(4px)' },
  modal: { position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%, -50%)',
           width: 'min(380px, calc(100vw - 32px))', zIndex: 71,
           background: '#0d0d10', border: '1px solid #26262e', borderRadius: 14,
           display: 'flex', flexDirection: 'column' },
  modalHd: { padding: '16px 18px 12px', borderBottom: '1px solid #1f1f25',
             display: 'flex', justifyContent: 'space-between', alignItems: 'center' },
  modalTitle: { fontSize: 15, fontWeight: 600, color: '#fafaf7' },
  modalX: { width: 26, height: 26, borderRadius: 6, border: '1px solid #2a2a31',
            background: '#15151a', color: '#a8a8b2', cursor: 'pointer', padding: 0,
            display: 'flex', alignItems: 'center', justifyContent: 'center' },
  modalBody: { padding: '16px 18px 18px', display: 'flex', flexDirection: 'column' },
  modalInput: { width: '100%', padding: '10px 12px', background: '#15151a',
                border: '1px solid #2a2a31', borderRadius: 8, color: '#fafaf7',
                fontSize: 14, outline: 'none', fontFamily: 'inherit' },
  modalErr: { marginTop: 8, fontSize: 11.5, color: '#f87171' },
  modalBtn: { marginTop: 14, padding: '10px', background: 'var(--accent)', border: 0,
              borderRadius: 8, color: '#0a0a0c', fontSize: 13, fontWeight: 600, cursor: 'pointer' },
};

Object.assign(window, {
  flowSync: {
    getClient,
    useSession, useProfile, loadProfile,
    ensureProfile, signInWithPassword, signOut,
    uploadTest, deleteTest, pullAndMerge, pushAll,
    CloudButton,
  },
});
