// app.jsx — Drive Cleaner root component
// Renders Setup screen until "signed in" (mock), then Dashboard.

const { useState: useS, useEffect: useE, useMemo, useRef: useR } = React;

const STORAGE_KEY = 'drive-cleaner-v1';

function loadState() {
  try {
    const s = JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}');
    return {
      clientId: s.clientId || '',
      account: s.account || null,
      mode: s.mode || 'mock', // 'mock' | 'real'
      rules: Array.isArray(s.rules) ? s.rules : [],
      log: Array.isArray(s.log) ? s.log : [],
    };
  } catch { return { clientId: '', account: null, mode: 'mock', rules: [], log: [] }; }
}
function saveState(s) {
  localStorage.setItem(STORAGE_KEY, JSON.stringify(s));
}

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "mockFileCount": 120,
  "latencyMs": 250,
  "fastClock": false,
  "showNextRunBanner": true
}/*EDITMODE-END*/;

function nextRunAt(rule) {
  if (rule.trigger === 'manual') return null;
  const now = new Date();
  const [hh, mm] = (rule.triggerTime || '03:00').split(':').map(Number);
  if (rule.trigger === 'daily') {
    const d = new Date(now);
    d.setHours(hh, mm, 0, 0);
    if (d.getTime() <= now.getTime()) d.setDate(d.getDate() + 1);
    return d.getTime();
  }
  if (rule.trigger === 'monthly') {
    const d = new Date(now);
    d.setDate(rule.triggerDay || 1);
    d.setHours(hh, mm, 0, 0);
    if (d.getTime() <= now.getTime()) d.setMonth(d.getMonth() + 1);
    return d.getTime();
  }
  return null;
}

function formatWhen(ms) {
  if (!ms) return '—';
  const d = new Date(ms);
  const dd = Math.round((ms - Date.now()) / 60000);
  const human =
    dd < -60 * 24 ? `${Math.floor(-dd / 60 / 24)}d ago` :
    dd < -60 ? `${Math.floor(-dd / 60)}h ago` :
    dd < 0 ? `${-dd}m ago` :
    dd < 60 ? `in ${dd}m` :
    dd < 60 * 24 ? `in ${Math.floor(dd / 60)}h` :
    `in ${Math.floor(dd / 60 / 24)}d`;
  return human + ' · ' + d.toLocaleString([], { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' });
}

function Setup({ onSignedIn, initialClientId }) {
  const [clientId, setClientId] = useS(initialClientId || '');
  const [mode, setMode] = useS('real'); // 'real' | 'mock'
  const [busy, setBusy] = useS(false);
  const [error, setError] = useS(null);
  const looksValid = clientId.length > 12 && clientId.endsWith('.apps.googleusercontent.com');
  const origin = window.location.origin;

  async function signIn() {
    setBusy(true); setError(null);
    try {
      window.setDriveMode(mode);
      const acct = mode === 'real'
        ? await window.realDrive.signIn(clientId)
        : await window.mockDrive.signIn('demo@example.com');
      onSignedIn(clientId, acct, mode);
    } catch (e) {
      setError(e.message || String(e));
      // Revert to mock so the rest of the app stays usable
      window.setDriveMode('mock');
    } finally {
      setBusy(false);
    }
  }

  return (
    <div className="setup">
      <div className="card">
        <div className="brand">
          <span className="mark"></span>
          <span>Drive Cleaner</span>
          <span className="mock-pill" style={{ marginLeft: 'auto' }}>
            <span style={{ width: 6, height: 6, borderRadius: '50%', background: 'var(--accent)' }}></span>
            MOCK MODE
          </span>
        </div>
        <h1>Connect your Google account.</h1>
        <p className="lede">
          Drive Cleaner runs in your browser and uses your own Google OAuth credentials —
          we never see your password, and your access token stays in this tab.
        </p>

        <label className="field">
          <span className="lbl">Google OAuth Client ID</span>
          <input
            className="input mono"
            value={clientId}
            onChange={(e) => setClientId(e.target.value.trim())}
            placeholder="123456789-abc…apps.googleusercontent.com"
            autoFocus
          />
          <span className="help">
            {looksValid
              ? <>✓ Looks valid.</>
              : <>This prototype accepts any value ending in <code>.apps.googleusercontent.com</code> — paste a real Client ID, or use the test value below.</>}
          </span>
        </label>

        <label className="field">
          <span className="lbl">Mode</span>
          <div className="radio-row">
            <label className={mode === 'real' ? 'active' : ''}>
              <input type="radio" checked={mode === 'real'} onChange={() => setMode('real')} />
              Real Google account
            </label>
            <label className={mode === 'mock' ? 'active' : ''}>
              <input type="radio" checked={mode === 'mock'} onChange={() => setMode('mock')} />
              Demo (mock data)
            </label>
          </div>
          <span className="help">
            {mode === 'real'
              ? <>Pops the real Google OAuth dialog. Your Client ID must whitelist <code>{origin}</code> as an Authorized JavaScript origin.</>
              : <>No real auth — explore the UI against a fake Drive of generated files.</>}
          </span>
        </label>

        {error && (
          <div className="danger-callout" style={{ marginBottom: 14 }}>
            ⚠ {error}
          </div>
        )}

        <div style={{ display: 'flex', gap: 10, marginBottom: 16 }}>
          {mode === 'mock' && (
            <button
              className="btn"
              onClick={() => setClientId('123456789012-mockedforpreview.apps.googleusercontent.com')}
            >
              Use a test value
            </button>
          )}
          <button
            className="btn btn-primary"
            style={{ marginLeft: 'auto' }}
            disabled={!looksValid || busy}
            onClick={signIn}
          >
            {busy ? 'Authorizing…' : mode === 'real' ? 'Sign in with Google' : 'Continue with demo'}
          </button>
        </div>

        <details>
          <summary>How to create a Google OAuth Client ID</summary>
          <ol>
            <li>Open <a href="https://console.cloud.google.com/" target="_blank" rel="noreferrer">Google Cloud Console</a> and select or create a project.</li>
            <li>APIs &amp; Services → Library → enable <strong>Google Drive API</strong>.</li>
            <li>APIs &amp; Services → OAuth consent screen → set up External, add yourself as a test user.</li>
            <li>APIs &amp; Services → Credentials → <strong>Create credentials → OAuth client ID → Web application</strong>.</li>
            <li>Add an Authorized JavaScript origin: <code>{origin}</code></li>
            <li>Copy the Client ID and paste it above.</li>
          </ol>
        </details>
      </div>
    </div>
  );
}

function Topbar({ account, clientId, mockPill, nextRun, onDisconnect, onChangeClientId, mode }) {
  return (
    <div className="topbar">
      <div className="brand">
        <span className="mark"></span>
        <span>Drive Cleaner</span>
        {mode === 'mock' && (
          <span className="mock-pill" style={{ marginLeft: 10 }}>
            <span style={{ width: 6, height: 6, borderRadius: '50%', background: 'var(--accent)' }}></span>
            MOCK MODE
          </span>
        )}
        {mode === 'real' && (
          <span className="chip chip-ok" style={{ marginLeft: 10 }}>
            <span className="dot"></span>LIVE
          </span>
        )}
      </div>
      <div className="meta">
        {nextRun && (
          <span className="chip chip-accent" title="Next scheduled run">
            <span className="dot"></span>
            Next run {formatWhen(nextRun)}
          </span>
        )}
        <div>
          <div style={{ fontWeight: 500 }}>{account?.name}</div>
          <div className="mono" style={{ fontSize: 11, color: 'var(--fg-faint)' }}>{account?.email}</div>
        </div>
        <button className="btn btn-sm" onClick={onChangeClientId}>Change Client ID</button>
        <button className="btn btn-sm btn-ghost" onClick={onDisconnect}>Disconnect</button>
      </div>
    </div>
  );
}

function FolderCard({ rule, onPreview, onRunNow, onEdit, onRemove }) {
  const [pending, setPending] = useS(null);

  useE(() => {
    let alive = true;
    window.driveAPI.listMatching({
      folderId: rule.folderId,
      olderThanMs: ageToMs(rule.ageValue, rule.ageUnit),
      recursive: rule.recursive,
      mimeFilter: rule.mimeFilter,
    }).then((files) => {
      if (!alive) return;
      const size = files.reduce((a, f) => a + f.size, 0);
      setPending({ count: files.length, size });
    }).catch(() => {});
    return () => { alive = false; };
  }, [rule.folderId, rule.ageValue, rule.ageUnit, rule.recursive, rule.mimeFilter, rule.lastRunAt]);

  const triggerLabel =
    rule.trigger === 'manual' ? 'Manual' :
    rule.trigger === 'daily' ? `Daily at ${rule.triggerTime}` :
    `Monthly · day ${rule.triggerDay} at ${rule.triggerTime}`;

  const nextRun = nextRunAt(rule);
  const neverPreviewed = !rule.previewedAt;

  return (
    <div className="folder-card">
      <div className="row1">
        <div style={{ flex: 1, minWidth: 0 }}>
          <h3 className="name">
            {rule.folderName}
            {rule.dryRun && <span className="chip">DRY RUN</span>}
            {rule.mode === 'permanent' && <span className="chip chip-danger">PERMANENT</span>}
            {rule.recursive && <span className="chip">+ SUBFOLDERS</span>}
          </h3>
          <div className="path">{rule.folderPath} · <code>{rule.folderId}</code></div>
        </div>
      </div>

      <div className="rule">
        <div>
          <div className="k">Delete older than</div>
          <div className="v">{rule.ageValue} {rule.ageUnit}</div>
        </div>
        <div>
          <div className="k">Trigger</div>
          <div className="v">{triggerLabel}</div>
        </div>
        <div>
          <div className="k">Next run</div>
          <div className="v">{nextRun ? formatWhen(nextRun) : '—'}</div>
        </div>
        <div>
          <div className="k">Last run</div>
          <div className="v">{rule.lastRunAt ? formatWhen(rule.lastRunAt) : 'never'}</div>
        </div>
      </div>

      <div className="actions">
        <div className="pending">
          {pending == null
            ? <span style={{ color: 'var(--fg-faint)' }}>scanning…</span>
            : pending.count === 0
              ? <span style={{ color: 'var(--ok)' }}>✓ nothing to clean</span>
              : <><span className="num">{pending.count}</span> files · {formatBytes(pending.size)} pending</>}
        </div>
        <button className="btn btn-sm btn-ghost" onClick={onRemove}>Remove</button>
        <button className="btn btn-sm" onClick={onEdit}>Edit</button>
        <button className="btn btn-sm" onClick={onPreview}>Preview</button>
        <button
          className="btn btn-sm btn-primary"
          disabled={neverPreviewed || (pending && pending.count === 0)}
          title={neverPreviewed ? 'Preview at least once before running' : ''}
          onClick={onRunNow}
        >
          Run now
        </button>
      </div>
    </div>
  );
}

function ActivityLog({ entries, onClear, onExport }) {
  return (
    <div className="panel">
      <h3 style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
        <span>Activity</span>
        <span style={{ display: 'flex', gap: 4 }}>
          {entries.length > 0 && (
            <button className="btn btn-sm btn-ghost" onClick={onExport}>Export</button>
          )}
          {entries.length > 0 && (
            <button className="btn btn-sm btn-ghost" onClick={onClear}>Clear</button>
          )}
        </span>
      </h3>
      {entries.length === 0 ? (
        <div style={{ color: 'var(--fg-faint)', fontSize: 13 }}>
          No runs yet. Preview a folder and click "Run now" to see entries here.
        </div>
      ) : (
        entries.slice(0, 30).map((e, i) => (
          <div className="log-entry" key={i}>
            <div className="when">{new Date(e.at).toLocaleString()}</div>
            <div className="title">
              {e.dryRun && <span className="chip" style={{ marginRight: 6 }}>DRY</span>}
              {e.folderName} · {e.successCount} {e.mode === 'permanent' ? 'deleted' : 'trashed'}
              {e.failures > 0 && <span style={{ color: 'var(--danger)' }}> · {e.failures} failed</span>}
            </div>
            {e.files && e.files.length > 0 && (
              <details>
                <summary>{e.files.length} files</summary>
                <ul>
                  {e.files.slice(0, 8).map((f, j) => (<li key={j}>{f}</li>))}
                  {e.files.length > 8 && <li style={{ listStyle: 'none' }}>… and {e.files.length - 8} more</li>}
                </ul>
              </details>
            )}
          </div>
        ))
      )}
    </div>
  );
}

function Dashboard({ state, setState, tweaks }) {
  const [modal, setModal] = useS(null); // {kind: 'add'|'edit', initial?}
  const [preview, setPreview] = useS(null); // rule
  const [run, setRun] = useS(null); // {rule, files}

  function commitState(patch) {
    setState((prev) => {
      const next = typeof patch === 'function' ? patch(prev) : { ...prev, ...patch };
      saveState(next);
      return next;
    });
  }

  function saveRule(rule) {
    commitState((prev) => {
      const i = prev.rules.findIndex((r) => r.id === rule.id);
      const rules = i === -1 ? [...prev.rules, rule] : prev.rules.map((r) => r.id === rule.id ? rule : r);
      return { ...prev, rules };
    });
    setModal(null);
  }

  function removeRule(id) {
    if (!confirm('Remove this folder rule?')) return;
    commitState((prev) => ({ ...prev, rules: prev.rules.filter((r) => r.id !== id) }));
  }

  function openPreview(rule) {
    setPreview(rule);
  }

  function confirmPreview(files) {
    const rule = preview;
    commitState((prev) => ({
      ...prev,
      rules: prev.rules.map((r) => r.id === rule.id ? { ...r, previewedAt: Date.now() } : r),
    }));
    setPreview(null);
    setRun({ rule, files });
  }

  function runNow(rule) {
    // Auto-run path: scan + run without showing preview (uses last previewedAt as the gate)
    window.driveAPI.listMatching({
      folderId: rule.folderId,
      olderThanMs: ageToMs(rule.ageValue, rule.ageUnit),
      recursive: rule.recursive,
      mimeFilter: rule.mimeFilter,
    }).then((files) => {
      if (files.length > rule.perRunCap) {
        alert(`Per-run cap exceeded (${files.length} > ${rule.perRunCap}). Open Preview to review.`);
        return;
      }
      if (files.length === 0) return;
      setRun({ rule, files });
    });
  }

  function completeRun(successCount, failures) {
    const r = run.rule;
    const files = run.files;
    commitState((prev) => ({
      ...prev,
      rules: prev.rules.map((x) =>
        x.id === r.id ? { ...x, lastRunAt: Date.now(), lastRunSummary: { successCount, failures: failures.length } } : x,
      ),
      log: [{
        at: Date.now(),
        folderName: r.folderName,
        successCount,
        failures: failures.length,
        mode: r.mode,
        dryRun: r.dryRun,
        files: files.map((f) => f.name),
      }, ...prev.log].slice(0, 500),
    }));
    setRun(null);
  }

  // Scheduler
  useE(() => {
    const tick = () => {
      const now = Date.now();
      state.rules.forEach((r) => {
        if (r.trigger === 'manual') return;
        const next = nextRunAt(r);
        const last = r.lastRunAt || 0;
        // Only trigger if next time is past AND we haven't run within the last 60s
        // (simple guard for the mock; real implementation would store nextRunAt explicitly)
        if (next && next <= now && (now - last) > 60_000 && !run && !preview) {
          // Safety: only auto-run rules the user has previewed at least once
          if (!r.previewedAt) return;
          runNow(r);
        }
      });
    };
    const id = setInterval(tick, tweaks.fastClock ? 1000 : 30_000);
    return () => clearInterval(id);
  }, [state.rules, run, preview, tweaks.fastClock]);

  const soonest = state.rules
    .map(nextRunAt)
    .filter(Boolean)
    .sort((a, b) => a - b)[0];

  return (
    <>
      <Topbar
        account={state.account}
        clientId={state.clientId}
        mode={state.mode}
        nextRun={tweaks.showNextRunBanner ? soonest : null}
        onDisconnect={async () => {
          if (!confirm('Disconnect? Folder rules are kept.')) return;
          try { await window.driveAPI.signOut?.(); } catch {}
          window.setDriveMode('mock');
          commitState({ account: null });
        }}
        onChangeClientId={async () => {
          if (!confirm('Change Client ID? You will be signed out.')) return;
          try { await window.driveAPI.signOut?.(); } catch {}
          window.setDriveMode('mock');
          commitState({ account: null, clientId: '' });
        }}
      />

      <div className="layout">
        <div className="col-main">
          <h2 className="section-title">
            <span>Folders to clean</span>
            <span className="count">{state.rules.length} {state.rules.length === 1 ? 'rule' : 'rules'}</span>
          </h2>

          {state.rules.length === 0 ? (
            <div className="empty">
              <div style={{ fontSize: 15, color: 'var(--fg)', marginBottom: 8 }}>
                No folder rules yet.
              </div>
              <div style={{ marginBottom: 18 }}>
                Add a Drive folder, set an age threshold, and Drive Cleaner will tidy it up for you.
              </div>
              <button className="btn btn-primary" onClick={() => setModal({ kind: 'add' })}>
                + Add your first folder
              </button>
            </div>
          ) : (
            <>
              {state.rules.map((r) => (
                <FolderCard
                  key={r.id}
                  rule={r}
                  onPreview={() => openPreview(r)}
                  onRunNow={() => runNow(r)}
                  onEdit={() => setModal({ kind: 'edit', initial: r })}
                  onRemove={() => removeRule(r.id)}
                />
              ))}
              <div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: 8 }}>
                <button className="btn btn-primary" onClick={() => setModal({ kind: 'add' })}>
                  + Add new folder to clean
                </button>
              </div>
            </>
          )}
        </div>

        <div className="col-side">
          <ActivityLog
            entries={state.log}
            onClear={() => commitState({ log: [] })}
            onExport={() => {
              const blob = new Blob(
                [JSON.stringify({
                  exportedAt: new Date().toISOString(),
                  account: state.account?.email,
                  mode: state.mode,
                  entries: state.log,
                }, null, 2)],
                { type: 'application/json' },
              );
              const a = document.createElement('a');
              a.href = URL.createObjectURL(blob);
              a.download = `drive-cleaner-log-${new Date().toISOString().slice(0,10)}.json`;
              a.click();
              setTimeout(() => URL.revokeObjectURL(a.href), 1000);
            }}
          />
        </div>
      </div>

      {modal && (
        <FolderModal
          initial={modal.initial}
          onSave={saveRule}
          onClose={() => setModal(null)}
        />
      )}
      {preview && (
        <PreviewDrawer
          rule={preview}
          onClose={() => setPreview(null)}
          onConfirm={confirmPreview}
        />
      )}
      {run && (
        <RunModal
          rule={run.rule}
          files={run.files}
          onClose={() => setRun(null)}
          onComplete={completeRun}
        />
      )}
    </>
  );
}

function App() {
  const [state, setState] = useS(loadState);
  const [tweaks, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const [authPrompt, setAuthPrompt] = useS(null);

  // Persist state whenever it changes
  useE(() => { saveState(state); }, [state]);

  // Apply tweaks to mock drive only
  useE(() => {
    window.mockDrive.setLatency(tweaks.latencyMs);
    window.mockDrive.reset({ seed: 42, fileCount: tweaks.mockFileCount });
    if (state.mode === 'mock') setState((s) => ({ ...s, _refresh: Date.now() }));
  }, [tweaks.mockFileCount, tweaks.latencyMs]);

  // On boot: if we were in real mode last time, we need a fresh token.
  // Show a soft re-auth prompt rather than auto-popping the OAuth dialog.
  useE(() => {
    if (state.mode === 'real' && state.account && !window.realDrive.getAccount()) {
      setAuthPrompt({ clientId: state.clientId, account: state.account });
    }
    if (state.mode === 'mock' && state.account) {
      window.setDriveMode('mock');
    }
  }, []);

  async function resumeReal() {
    try {
      const acct = await window.realDrive.signIn(state.clientId);
      window.setDriveMode('real');
      setState((s) => ({ ...s, account: acct }));
      setAuthPrompt(null);
    } catch (e) {
      alert('Sign-in failed: ' + (e.message || e));
    }
  }

  function onSignedIn(clientId, account, mode) {
    setState((s) => {
      const next = { ...s, clientId, account, mode };
      saveState(next);
      return next;
    });
  }

  return (
    <>
      {state.account && !authPrompt ? (
        <Dashboard state={state} setState={setState} tweaks={tweaks} />
      ) : authPrompt ? (
        <div className="setup">
          <div className="card">
            <div className="brand">
              <span className="mark"></span>
              <span>Drive Cleaner</span>
            </div>
            <h1>Welcome back.</h1>
            <p className="lede">
              You were signed in as <strong>{authPrompt.account.email}</strong>. Your Google access token expired on reload — click below to refresh it.
            </p>
            <div style={{ display: 'flex', gap: 10 }}>
              <button className="btn btn-primary" onClick={resumeReal}>Resume session</button>
              <button className="btn btn-ghost" onClick={() => {
                setState((s) => ({ ...s, account: null, mode: 'mock' }));
                setAuthPrompt(null);
              }}>Start over</button>
            </div>
          </div>
        </div>
      ) : (
        <Setup onSignedIn={onSignedIn} initialClientId={state.clientId} />
      )}

      <TweaksPanel title="Tweaks">
        <TweakSection label="Mock Drive" />
        <TweakSlider
          label="Files in mock Drive"
          value={tweaks.mockFileCount}
          min={20} max={500} step={10}
          onChange={(v) => setTweak('mockFileCount', v)}
        />
        <TweakSlider
          label="Simulated API latency"
          value={tweaks.latencyMs}
          min={0} max={1500} step={50} unit="ms"
          onChange={(v) => setTweak('latencyMs', v)}
        />
        <TweakSection label="Scheduling" />
        <TweakToggle
          label="Fast clock (poll every 1s)"
          value={tweaks.fastClock}
          onChange={(v) => setTweak('fastClock', v)}
        />
        <TweakToggle
          label="Show next-run banner"
          value={tweaks.showNextRunBanner}
          onChange={(v) => setTweak('showNextRunBanner', v)}
        />
        <TweakSection label="Reset" />
        <TweakButton
          label="Clear all rules + log"
          onClick={() => {
            if (!confirm('Wipe local state?')) return;
            localStorage.removeItem(STORAGE_KEY);
            location.reload();
          }}
        />
      </TweaksPanel>
    </>
  );
}

ReactDOM.createRoot(document.getElementById('root')).render(<App />);
