// sync.jsx — Cloud backup & sync (Web Share + Google Drive appDataFolder)
//
// Two strategies, both mobile-friendly:
// 1. Native Share Sheet — tap "Backup", pick Drive/Email/anywhere
// 2. Google Drive auto-sync via appDataFolder (no picker needed)

const SYNC_STORAGE_KEY = 'habit-ican-sync-v1';
const GIS_SRC = 'https://accounts.google.com/gsi/client';
const DRIVE_SCOPE = 'https://www.googleapis.com/auth/drive.appdata';
const DRIVE_FILE_NAME = 'habit-ican-data.json';

// ── Sync state persistence ─────────────────────────
function loadSyncConfig() {
  try {
    return JSON.parse(localStorage.getItem(SYNC_STORAGE_KEY) || '{}');
  } catch { return {}; }
}
function saveSyncConfig(cfg) {
  try { localStorage.setItem(SYNC_STORAGE_KEY, JSON.stringify(cfg)); } catch {}
}

// ── Strategy 1: Native Share Sheet ─────────────────
async function backupViaShareSheet(state) {
  const filename = `habit-ican-${toDateKey(new Date())}.json`;
  const content = JSON.stringify(state, null, 2);
  const file = new File([content], filename, { type: 'application/json' });

  // Try Web Share Level 2 (with files) — works on Android Chrome
  if (navigator.canShare && navigator.canShare({ files: [file] })) {
    try {
      await navigator.share({
        files: [file],
        title: 'Backup Habit Ican',
        text: `Backup data Habit Ican — ${toDateKey(new Date())}`,
      });
      return { ok: true, method: 'share' };
    } catch (e) {
      if (e.name === 'AbortError') return { ok: false, cancelled: true };
      // fallback to download
    }
  }
  // Fallback: download as file (desktop, or unsupported)
  const url = URL.createObjectURL(file);
  const a = document.createElement('a');
  a.href = url; a.download = filename;
  document.body.appendChild(a); a.click(); document.body.removeChild(a);
  setTimeout(() => URL.revokeObjectURL(url), 1000);
  return { ok: true, method: 'download' };
}

async function restoreFromFile(file) {
  const text = await file.text();
  const data = JSON.parse(text);
  if (!data.habits || !data.checks) throw new Error('File tidak valid');
  return data;
}

// ── Strategy 2: Google Drive (appDataFolder) ─────────
// Load GIS script once
let gisLoadPromise = null;
function loadGIS() {
  if (gisLoadPromise) return gisLoadPromise;
  gisLoadPromise = new Promise((resolve, reject) => {
    if (window.google?.accounts?.oauth2) return resolve();
    const s = document.createElement('script');
    s.src = GIS_SRC; s.async = true; s.defer = true;
    s.onload = () => resolve();
    s.onerror = () => reject(new Error('Gagal memuat Google Identity'));
    document.head.appendChild(s);
  });
  return gisLoadPromise;
}

// Token client — singleton per page load
let tokenClient = null;
let currentToken = null;
let tokenExpiresAt = 0;

function initTokenClient(clientId) {
  if (!window.google?.accounts?.oauth2) throw new Error('GIS belum siap');
  return new Promise((resolve) => {
    tokenClient = window.google.accounts.oauth2.initTokenClient({
      client_id: clientId,
      scope: DRIVE_SCOPE,
      callback: (resp) => {
        if (resp.error) { resolve({ ok: false, error: resp.error }); return; }
        currentToken = resp.access_token;
        tokenExpiresAt = Date.now() + (resp.expires_in - 60) * 1000;
        resolve({ ok: true, token: resp.access_token });
      },
    });
    resolve({ ok: true, ready: true });
  });
}

async function requestToken({ silent = false } = {}) {
  if (currentToken && Date.now() < tokenExpiresAt) return currentToken;
  if (!tokenClient) throw new Error('Belum diinisialisasi');
  return new Promise((resolve, reject) => {
    tokenClient.callback = (resp) => {
      if (resp.error) reject(new Error(resp.error));
      else {
        currentToken = resp.access_token;
        tokenExpiresAt = Date.now() + (resp.expires_in - 60) * 1000;
        resolve(resp.access_token);
      }
    };
    try {
      tokenClient.requestAccessToken({ prompt: silent ? '' : 'consent' });
    } catch (e) { reject(e); }
  });
}

async function driveApi(path, opts = {}) {
  const token = currentToken || await requestToken({ silent: true });
  const url = path.startsWith('http') ? path : `https://www.googleapis.com/drive/v3${path}`;
  const r = await fetch(url, {
    ...opts,
    headers: { Authorization: `Bearer ${token}`, ...opts.headers },
  });
  if (!r.ok) {
    const txt = await r.text();
    throw new Error(`Drive API ${r.status}: ${txt}`);
  }
  return r;
}

async function findDriveFile() {
  const r = await driveApi(`/files?spaces=appDataFolder&fields=files(id,name,modifiedTime)&q=name='${DRIVE_FILE_NAME}'`);
  const data = await r.json();
  return data.files?.[0] || null;
}

async function createDriveFile(state) {
  const metadata = { name: DRIVE_FILE_NAME, parents: ['appDataFolder'] };
  const boundary = '-------habit-ican-' + Math.random().toString(36).slice(2);
  const body = [
    `--${boundary}`,
    'Content-Type: application/json; charset=UTF-8',
    '',
    JSON.stringify(metadata),
    `--${boundary}`,
    'Content-Type: application/json',
    '',
    JSON.stringify(state),
    `--${boundary}--`,
  ].join('\r\n');
  const r = await driveApi(
    'https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&fields=id,modifiedTime',
    {
      method: 'POST',
      headers: { 'Content-Type': `multipart/related; boundary=${boundary}` },
      body,
    },
  );
  return r.json();
}

async function updateDriveFile(fileId, state) {
  const r = await driveApi(
    `https://www.googleapis.com/upload/drive/v3/files/${fileId}?uploadType=media&fields=id,modifiedTime`,
    {
      method: 'PATCH',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(state),
    },
  );
  return r.json();
}

async function downloadDriveFile(fileId) {
  const r = await driveApi(`/files/${fileId}?alt=media`);
  return r.json();
}

// High-level operations
async function driveConnect(clientId) {
  await loadGIS();
  await initTokenClient(clientId);
  await requestToken(); // shows consent on first call
  const file = await findDriveFile();
  return { ok: true, file };
}

async function drivePush(state) {
  const cfg = loadSyncConfig();
  let file;
  if (cfg.fileId) {
    try {
      file = await updateDriveFile(cfg.fileId, state);
    } catch (e) {
      // file might be deleted, create new
      file = await createDriveFile(state);
    }
  } else {
    file = await createDriveFile(state);
  }
  saveSyncConfig({ ...cfg, fileId: file.id, lastPushed: Date.now(), cloudModified: file.modifiedTime });
  return file;
}

async function drivePull() {
  const cfg = loadSyncConfig();
  if (!cfg.fileId) {
    const file = await findDriveFile();
    if (!file) throw new Error('Belum ada backup di Drive');
    saveSyncConfig({ ...cfg, fileId: file.id });
    cfg.fileId = file.id;
  }
  const state = await downloadDriveFile(cfg.fileId);
  return state;
}

// ── SyncPanel React UI ─────────────────────────────
function SyncPanel({ state, setState }) {
  const [cfg, setCfg] = React.useState(() => loadSyncConfig());
  const [clientIdInput, setClientIdInput] = React.useState(cfg.clientId || '');
  const [status, setStatus] = React.useState('idle');
  const [message, setMessage] = React.useState('');
  const [showSetup, setShowSetup] = React.useState(false);

  const updateCfg = (patch) => {
    const next = { ...loadSyncConfig(), ...patch };
    saveSyncConfig(next);
    setCfg(next);
  };

  const reportError = (e) => {
    setStatus('error');
    setMessage(e.message || String(e));
    setTimeout(() => { setStatus('idle'); setMessage(''); }, 5000);
  };
  const reportSuccess = (msg) => {
    setStatus('success');
    setMessage(msg);
    setTimeout(() => { setStatus('idle'); setMessage(''); }, 3000);
  };

  // ── Share Sheet handlers
  const onBackupShare = async () => {
    setStatus('working');
    try {
      const r = await backupViaShareSheet(state);
      if (r.cancelled) { setStatus('idle'); return; }
      reportSuccess(r.method === 'share' ? 'Dibagikan via Share Sheet' : 'File ter-download');
    } catch (e) { reportError(e); }
  };

  const fileInputRef = React.useRef(null);
  const onRestoreFile = async (e) => {
    const file = e.target.files?.[0];
    if (!file) return;
    if (!confirm('Ganti semua data dengan file ini? Data saat ini akan ditimpa.')) {
      e.target.value = ''; return;
    }
    setStatus('working');
    try {
      const data = await restoreFromFile(file);
      setState(data);
      reportSuccess('Data berhasil dipulihkan');
    } catch (err) { reportError(err); }
    e.target.value = '';
  };

  // ── Google Drive handlers
  const onConnect = async () => {
    if (!clientIdInput.trim()) {
      setMessage('Masukkan Client ID dulu');
      setStatus('error'); return;
    }
    setStatus('working'); setMessage('Menghubungkan...');
    try {
      const trimmed = clientIdInput.trim();
      updateCfg({ clientId: trimmed });
      const r = await driveConnect(trimmed);
      if (r.file) {
        updateCfg({ fileId: r.file.id, cloudModified: r.file.modifiedTime, connectedAt: Date.now() });
        const cloudTime = new Date(r.file.modifiedTime).getTime();
        const localTime = cfg.lastPushed || 0;
        if (cloudTime > localTime && Object.keys(state.checks).length === 0) {
          if (confirm('Ditemukan backup di Drive. Muat sekarang?')) {
            const data = await drivePull();
            setState(data);
            reportSuccess('Data dimuat dari Drive');
          }
        } else if (cloudTime > localTime) {
          if (confirm(`Backup di Drive lebih baru (${new Date(r.file.modifiedTime).toLocaleString('id-ID')}). Muat sekarang? Data lokal akan ditimpa.`)) {
            const data = await drivePull();
            setState(data);
            reportSuccess('Data dimuat dari Drive');
          } else {
            reportSuccess('Terhubung — data lokal dipertahankan');
          }
        } else {
          reportSuccess('Terhubung ke Google Drive');
        }
      } else {
        updateCfg({ connectedAt: Date.now() });
        // No file yet — push first
        await drivePush(state);
        reportSuccess('Terhubung & backup pertama berhasil');
      }
    } catch (e) { reportError(e); }
  };

  const onPush = async () => {
    setStatus('working');
    try { await drivePush(state); reportSuccess('Tersimpan ke Drive'); }
    catch (e) { reportError(e); }
  };
  const onPull = async () => {
    if (!confirm('Tarik data dari Drive? Data lokal akan ditimpa.')) return;
    setStatus('working');
    try {
      const data = await drivePull();
      setState(data);
      reportSuccess('Data dimuat dari Drive');
    } catch (e) { reportError(e); }
  };
  const onDisconnect = () => {
    if (!confirm('Putus dari Drive? Data tetap aman di HP dan di Drive (tidak dihapus).')) return;
    saveSyncConfig({});
    setCfg({});
    setClientIdInput('');
    currentToken = null; tokenExpiresAt = 0;
    reportSuccess('Diputus');
  };

  // ── Auto-sync (debounced push on state change)
  const stateRef = React.useRef(state);
  React.useEffect(() => { stateRef.current = state; }, [state]);
  const autoTimerRef = React.useRef(null);
  React.useEffect(() => {
    if (!cfg.autoSync || !cfg.clientId || !cfg.fileId) return;
    // Debounced push
    if (autoTimerRef.current) clearTimeout(autoTimerRef.current);
    autoTimerRef.current = setTimeout(async () => {
      try {
        await drivePush(stateRef.current);
        updateCfg({ lastPushed: Date.now() });
      } catch (e) { /* silent */ }
    }, 8000); // 8s debounce
    return () => clearTimeout(autoTimerRef.current);
  }, [state, cfg.autoSync, cfg.clientId, cfg.fileId]);

  const isConnected = !!(cfg.clientId && cfg.connectedAt);
  const lastSyncText = cfg.lastPushed
    ? `Tersimpan ${formatRelative(cfg.lastPushed)}`
    : isConnected ? 'Belum sync' : '';

  return (
    <div className="sync-block">
      {/* === Strategy 1: Manual Backup === */}
      <div className="sync-section">
        <div className="sync-section-title">
          <span>📤 Backup Manual</span>
          <span className="sync-section-sub">Simpan ke Drive/Email lewat Share Sheet HP</span>
        </div>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
          <button className="btn-secondary" onClick={onBackupShare} disabled={status === 'working'}>
            📤 Bagikan Backup
            <span style={{ color: 'var(--muted)', fontSize: 11, marginLeft: 6 }}>
              · Drive / Email / Apa saja
            </span>
          </button>
          <label className="btn-secondary" style={{ cursor: 'pointer', textAlign: 'center' }}>
            📥 Pulihkan dari File
            <input ref={fileInputRef} type="file" accept=".json" style={{ display: 'none' }} onChange={onRestoreFile}/>
          </label>
        </div>
      </div>

      {/* === Strategy 2: Auto-sync Drive === */}
      <div className="sync-section">
        <div className="sync-section-title">
          <span>☁️ Auto-Sync Google Drive</span>
          <span className="sync-section-sub">Sinkron otomatis HP ↔ Desktop</span>
        </div>

        {!isConnected ? (
          <>
            {!showSetup ? (
              <button className="btn-secondary" onClick={() => setShowSetup(true)}>
                Aktifkan Sync Drive
              </button>
            ) : (
              <div className="sync-setup">
                <div style={{ fontSize: 12, color: 'var(--muted)', marginBottom: 8, lineHeight: 1.5 }}>
                  Butuh <b>Google OAuth Client ID</b>. Setup sekali ~5 menit.
                  Panduan lengkap di file <b>SYNC-CLOUD.md</b>.
                </div>
                <input
                  className="form-input form-input-sm"
                  placeholder="Tempel Client ID di sini..."
                  value={clientIdInput}
                  onChange={(e) => setClientIdInput(e.target.value)}
                  style={{ marginBottom: 8 }}
                />
                <div style={{ display: 'flex', gap: 6 }}>
                  <button className="btn-secondary" onClick={() => setShowSetup(false)} style={{ flex: 1 }}>
                    Batal
                  </button>
                  <button className="btn-primary" onClick={onConnect}
                    disabled={!clientIdInput.trim() || status === 'working'}
                    style={{ flex: 2, padding: 12, fontSize: 14 }}>
                    {status === 'working' ? 'Menghubungkan...' : 'Hubungkan'}
                  </button>
                </div>
              </div>
            )}
          </>
        ) : (
          <>
            <div className="sync-status-card">
              <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
                <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                  <span style={{
                    width: 8, height: 8, borderRadius: 4,
                    background: status === 'working' ? '#C99A3C' : 'var(--accent)',
                    animation: status === 'working' ? 'pulse 1s infinite' : 'none',
                  }}/>
                  <span style={{ fontSize: 13, fontWeight: 500, color: 'var(--ink)' }}>
                    {status === 'working' ? 'Menyinkronkan...' : 'Terhubung'}
                  </span>
                </div>
                <span style={{ fontSize: 11, color: 'var(--muted)' }}>{lastSyncText}</span>
              </div>
            </div>
            <div className="toggle-row">
              <span style={{ fontSize: 14 }}>Auto-sync (8 detik setelah edit)</span>
              <button className={`toggle ${cfg.autoSync ? 'is-on' : ''}`}
                onClick={() => updateCfg({ autoSync: !cfg.autoSync })}>
                <span className="toggle-knob"/>
              </button>
            </div>
            <div style={{ display: 'flex', gap: 6, marginTop: 8 }}>
              <button className="btn-secondary" onClick={onPush} disabled={status === 'working'} style={{ flex: 1 }}>
                ⬆ Push Sekarang
              </button>
              <button className="btn-secondary" onClick={onPull} disabled={status === 'working'} style={{ flex: 1 }}>
                ⬇ Pull dari Drive
              </button>
            </div>
            <button className="text-btn" onClick={onDisconnect}
              style={{ marginTop: 10, padding: '6px 0', color: 'var(--muted)', textAlign: 'left' }}>
              Putuskan koneksi Drive
            </button>
          </>
        )}
      </div>

      {/* Status message */}
      {message && (
        <div className={`sync-toast sync-toast-${status}`}>
          {message}
        </div>
      )}
    </div>
  );
}

function formatRelative(ts) {
  const s = Math.floor((Date.now() - ts) / 1000);
  if (s < 60) return `${s} detik lalu`;
  if (s < 3600) return `${Math.floor(s/60)} menit lalu`;
  if (s < 86400) return `${Math.floor(s/3600)} jam lalu`;
  return `${Math.floor(s/86400)} hari lalu`;
}

Object.assign(window, {
  SyncPanel,
  backupViaShareSheet, restoreFromFile,
  driveConnect, drivePush, drivePull,
  loadSyncConfig, saveSyncConfig,
});
