// ===== m9-offline.jsx =====
// M9 · Offline capture & sync — FR11

const {
  React,
  Card, Btn, IconBtn, StatusChip, Avatar, Kpi, Field, Input, Textarea, Select, Checkbox, Toggle, Tabs, Empty, SectionHead, TopBar, Progress,
  IconPlus, IconCheck, IconX, IconArrow, IconArrowL, IconDownload, IconUpload, IconBook, IconClock, IconUsers,
  IconAward, IconShield, IconSearch, IconFilter, IconBell, IconStar, IconEdit, IconExternal, IconMore, IconMail, IconFlag, IconCal, IconClipboard, IconPin,
  VOLUNTEERS,
} = window;
const { useState: useOff9 } = React;

const off_over = { fontSize: 11, letterSpacing: "0.12em", textTransform: "uppercase", color: "var(--fg-3)", fontWeight: 500 };
const off_meta = { fontSize: 11, color: "var(--fg-3)" };

// ============================================================
// DATA
// ============================================================
const SYNC_QUEUE = [
  { id: "q-1", kind: "attendance", record: "Sasha Arenas — Cherry Creek cleanup · Jun 14", capturedBy: "Marcus Chen", capturedAt: "Jun 14, 9:04 am", state: "synced",   syncedAt: "Jun 14, 11:12 am", device: "iPhone — Marcus" },
  { id: "q-2", kind: "attendance", record: "Maya Okonkwo — Cherry Creek cleanup · Jun 14", capturedBy: "Marcus Chen", capturedAt: "Jun 14, 9:06 am", state: "synced",   syncedAt: "Jun 14, 11:12 am", device: "iPhone — Marcus" },
  { id: "q-3", kind: "hours",      record: "Esteban Mendez — 3.5 hr cleanup",              capturedBy: "Volunteer self-log", capturedAt: "Jun 14, 12:42 pm", state: "synced",   syncedAt: "Jun 14, 4:18 pm",  device: "Android — Esteban" },
  { id: "q-4", kind: "no-show",    record: "Rae Kowalski — Cherry Creek (no-show)",         capturedBy: "Marcus Chen", capturedAt: "Jun 14, 9:30 am", state: "synced",   syncedAt: "Jun 14, 11:12 am", device: "iPhone — Marcus" },
  { id: "q-5", kind: "outcome",    record: "Trees planted: 14 · Cherry Creek cleanup",      capturedBy: "Marcus Chen", capturedAt: "Jun 14, 11:42 am",state: "pending",  syncedAt: null, device: "iPhone — Marcus" },
  { id: "q-6", kind: "hours",      record: "Ben Liu — 4.0 hr trail crew",                    capturedBy: "Volunteer self-log", capturedAt: "Today, 8:22 am",   state: "pending",  syncedAt: null, device: "Android — Ben" },
  { id: "q-7", kind: "attendance", record: "Robert Fischer — Lake Pueblo · Jun 14",         capturedBy: "Priya Sandoval", capturedAt: "Jun 14, 8:48 am", state: "conflict", syncedAt: null, device: "iPad — Priya", conflict: "Server already has 'no-show' for this shift from Marcus. Yours says 'present'." },
  { id: "q-8", kind: "hours",      record: "Maya Okonkwo — 9.5 hr Hunter ed",                capturedBy: "Volunteer self-log", capturedAt: "Yest, 6:12 pm", state: "failed",   syncedAt: null, device: "iPhone — Maya", error: "Validation: hours exceed shift duration of 8 hr." },
  { id: "q-9", kind: "outcome",    record: "Visitors contacted: 86 · Naturalist program",   capturedBy: "Aliyah Chen", capturedAt: "Sun, 4:18 pm",  state: "synced",   syncedAt: "Sun, 6:02 pm",   device: "iPad — Aliyah" },
];

const DEVICES = [
  { id: "d-1", who: "Marcus Chen",     role: "Coordinator",  device: "iPhone 15 · iOS 18", cacheSize: "8.4 MB", cachedSince: "Jun 14, 5:00 am", expires: "Jun 21",  state: "trusted",  lastSync: "Jun 14, 11:12 am" },
  { id: "d-2", who: "Priya Sandoval", role: "Regional admin",device: "iPad Pro · iOS 18",   cacheSize: "32.1 MB",cachedSince: "Jun 13, 7:00 am", expires: "Jun 20",  state: "trusted",  lastSync: "Jun 14, 8:48 am · conflict" },
  { id: "d-3", who: "Aliyah Chen",     role: "Tenant admin",  device: "iPad Air · iOS 18",   cacheSize: "14.2 MB",cachedSince: "Jun 14, 6:00 am", expires: "Jun 21",  state: "trusted",  lastSync: "Sun, 6:02 pm" },
  { id: "d-4", who: "Esteban Mendez",  role: "Volunteer",      device: "Android · Pixel 8",   cacheSize: "1.4 MB", cachedSince: "Jun 14, 8:00 am", expires: "Jun 17",  state: "trusted",  lastSync: "Jun 14, 4:18 pm" },
  { id: "d-5", who: "Ben Liu",         role: "Volunteer",      device: "Android · Galaxy",     cacheSize: "1.1 MB", cachedSince: "Today, 6:14 am",   expires: "May 30",  state: "trusted",  lastSync: "Today, 8:22 am" },
  { id: "d-6", who: "Maya Okonkwo",    role: "Volunteer",      device: "iPhone 14 · iOS 17",  cacheSize: "920 KB", cachedSince: "Yest, 5:00 pm",   expires: "May 30", state: "blocked",  lastSync: "Yest, 6:12 pm · failed", note: "Trust revoked after 5 failed sync attempts." },
];

// ============================================================
// ROOT
// ============================================================
const OrgOffline = ({ go }) => {
  const [tab, setTab] = useOff9("queue");
  const queueCounts = SYNC_QUEUE.reduce((a, q) => { a[q.state] = (a[q.state] || 0) + 1; return a; }, {});
  return (
    <>
      <TopBar title="Offline sync"
        subtitle={`${SYNC_QUEUE.length} items in queue · ${queueCounts.conflict || 0} conflict${(queueCounts.conflict || 0) === 1 ? "" : "s"} · ${queueCounts.failed || 0} failed · ${DEVICES.filter(d => d.state === "trusted").length} trusted devices`}
        primary={<Btn icon={IconCheck}>Run sync now</Btn>}
        secondary={<Btn kind="secondary" icon={IconDownload}>Export sync log</Btn>} />
      <div style={{ flex: 1, overflow: "auto", padding: "20px 32px 56px" }}>
        <div style={{ display: "grid", gridTemplateColumns: "repeat(5, 1fr)", gap: 14, marginBottom: 22 }}>
          <Kpi label="Synced" value={String(queueCounts.synced || 0)} delta="last 24 hr" tone="up" icon={IconCheck} />
          <Kpi label="Pending" value={String(queueCounts.pending || 0)} delta="awaiting sync" tone="neutral" icon={IconClock} />
          <Kpi label="Conflict" value={String(queueCounts.conflict || 0)} delta="needs review" tone="down" icon={IconShield} />
          <Kpi label="Failed" value={String(queueCounts.failed || 0)} delta="validation errors" tone="down" icon={IconX} />
          <Kpi label="Devices trusted" value={String(DEVICES.filter(d => d.state === "trusted").length)} delta={`${DEVICES.filter(d => d.state === "blocked").length} blocked`} tone="neutral" icon={IconShield} />
        </div>

        <Tabs active={tab} onChange={setTab} tabs={[
          { id: "queue",     label: "Sync queue",       count: SYNC_QUEUE.length },
          { id: "conflicts", label: "Conflicts",        count: queueCounts.conflict || 0 },
          { id: "devices",   label: "Devices & cache",   count: DEVICES.length },
          { id: "policy",    label: "Policy" },
        ]} />

        {tab === "queue"     && <QueueTab go={go} />}
        {tab === "conflicts" && <ConflictsTab go={go} />}
        {tab === "devices"   && <DevicesTab />}
        {tab === "policy"    && <PolicyTab />}
      </div>
    </>
  );
};

// ---- QUEUE ----
const QueueTab = ({ go }) => (
  <>
    <Card padded={false}>
      <div style={{ display: "grid", gridTemplateColumns: "120px minmax(220px, 1.8fr) 160px 130px 130px 110px 36px", gap: 14, padding: "12px 18px", fontSize: 11, letterSpacing: "0.1em", textTransform: "uppercase", color: "var(--fg-3)", borderBottom: "1px solid var(--border)" }}>
        <span>Kind</span><span>Record</span><span>Captured by</span><span>Captured at</span><span>Device</span><span>State</span><span />
      </div>
      {SYNC_QUEUE.map((q, i, arr) => (
        <div key={q.id} style={{ display: "grid", gridTemplateColumns: "120px minmax(220px, 1.8fr) 160px 130px 130px 110px 36px", gap: 14, padding: "13px 18px", alignItems: "start", borderBottom: i === arr.length - 1 ? "none" : "1px solid var(--border-soft)", fontSize: 13 }}>
          <span style={{ fontSize: 11, padding: "2px 8px", borderRadius: 999, background: kindBg(q.kind), color: kindFg(q.kind), fontWeight: 500, textTransform: "capitalize", alignSelf: "start" }}>{q.kind}</span>
          <div>
            <div style={{ fontWeight: 500 }}>{q.record}</div>
            {q.conflict && <div style={{ fontSize: 11, color: "var(--crimson-600)", marginTop: 4, lineHeight: 1.4 }}>⚠ {q.conflict}</div>}
            {q.error && <div style={{ fontSize: 11, color: "var(--amber-600)", marginTop: 4, lineHeight: 1.4 }}>⚠ {q.error}</div>}
          </div>
          <span style={{ ...off_meta }}>{q.capturedBy}</span>
          <span style={{ ...off_meta }}>{q.capturedAt}</span>
          <span style={{ ...off_meta }}>{q.device}</span>
          <StatusChip
            status={q.state === "synced" ? "confirmed" : q.state === "pending" ? "info" : q.state === "conflict" ? "expired" : "pending"}
            size="sm"
            label={q.state[0].toUpperCase() + q.state.slice(1)} />
          <IconBtn icon={IconMore} size={28} />
        </div>
      ))}
    </Card>
    <div style={{ marginTop: 14, padding: 12, background: "var(--paper-soft)", borderRadius: 10, fontSize: 12, color: "var(--fg-2)", lineHeight: 1.5, border: "1px solid var(--border-soft)" }}>
      <IconShield size={13} style={{ verticalAlign: "middle", marginRight: 6, color: "var(--fg-3)" }} />
      Sync is idempotent — re-trying a record never produces a duplicate. Audit log preserves all sync attempts. Sensitive data is encrypted on-device and purged when the cache expires.
    </div>
  </>
);

function kindBg(k) {
  if (k === "attendance") return "var(--sage-100)";
  if (k === "hours")       return "var(--coral-100)";
  if (k === "outcome")     return "var(--iris-100)";
  if (k === "no-show")     return "var(--amber-100)";
  return "var(--paper-deep)";
}
function kindFg(k) {
  if (k === "attendance") return "var(--sage-700)";
  if (k === "hours")       return "var(--coral-700)";
  if (k === "outcome")     return "var(--iris-600)";
  if (k === "no-show")     return "var(--amber-600)";
  return "var(--fg-2)";
}

// ---- CONFLICTS ----
const ConflictsTab = ({ go }) => {
  const conflicts = SYNC_QUEUE.filter(q => q.state === "conflict");
  return (
    <>
      <p style={{ fontSize: 13, color: "var(--fg-2)", marginBottom: 14, maxWidth: 720 }}>
        Conflicts happen when two devices captured contradicting state for the same record while offline. Pick the right version, or merge if both contain unique info. Both versions are kept in the audit log.
      </p>
      {conflicts.length === 0 && <Card><Empty title="No conflicts" body="All records are synced cleanly." icon={IconCheck} /></Card>}
      {conflicts.map(c => (
        <Card key={c.id} style={{ marginBottom: 14 }}>
          <div style={{ display: "flex", justifyContent: "space-between", alignItems: "start", marginBottom: 14 }}>
            <div>
              <span style={{ fontSize: 11, padding: "2px 8px", borderRadius: 999, background: kindBg(c.kind), color: kindFg(c.kind), fontWeight: 500, textTransform: "capitalize" }}>{c.kind}</span>
              <div style={{ fontSize: 15, fontWeight: 500, marginTop: 8 }}>{c.record}</div>
              <div style={{ ...off_meta, marginTop: 4 }}>{c.conflict}</div>
            </div>
            <Btn size="sm" kind="ghost" icon={IconExternal}>Open record</Btn>
          </div>
          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }}>
            <Card style={{ background: "var(--paper)", padding: 14 }}>
              <div style={{ ...off_over, marginBottom: 8 }}>Server (already synced)</div>
              <div style={{ fontSize: 13, marginBottom: 4 }}><strong>State:</strong> No-show</div>
              <div style={{ ...off_meta }}>Captured by Marcus Chen · Jun 14 · 9:30 am · iPhone</div>
              <Btn size="sm" kind="secondary" style={{ marginTop: 12 }}>Keep server version</Btn>
            </Card>
            <Card style={{ background: "var(--paper)", padding: 14, border: "1px solid var(--coral-500)", borderWidth: 2 }}>
              <div style={{ ...off_over, marginBottom: 8, color: "var(--coral-700)" }}>Your offline capture</div>
              <div style={{ fontSize: 13, marginBottom: 4 }}><strong>State:</strong> Present</div>
              <div style={{ ...off_meta }}>Captured by Priya Sandoval · Jun 14 · 8:48 am · iPad</div>
              <Btn size="sm" style={{ marginTop: 12 }}>Use your version</Btn>
            </Card>
          </div>
          <div style={{ marginTop: 14, display: "flex", gap: 8 }}>
            <Btn size="sm" kind="ghost">Mark as duplicate</Btn>
            <Btn size="sm" kind="ghost">Discuss with team</Btn>
            <div style={{ marginLeft: "auto" }}>
              <Btn size="sm" kind="secondary">Defer 24 hr</Btn>
            </div>
          </div>
        </Card>
      ))}
    </>
  );
};

// ---- DEVICES ----
const DevicesTab = () => (
  <>
    <p style={{ fontSize: 13, color: "var(--fg-2)", marginBottom: 14, maxWidth: 720 }}>
      Devices with offline cache enabled. Trust can be revoked at any time — cache is invalidated within 5 minutes and the device is forced to re-authenticate. Cache expires automatically per tenant policy.
    </p>
    <Card padded={false}>
      <div style={{ display: "grid", gridTemplateColumns: "minmax(180px, 1.2fr) minmax(160px, 1fr) minmax(180px, 1.2fr) 110px 130px 130px 110px 36px", gap: 14, padding: "12px 18px", fontSize: 11, letterSpacing: "0.1em", textTransform: "uppercase", color: "var(--fg-3)", borderBottom: "1px solid var(--border)" }}>
        <span>Who</span><span>Role</span><span>Device</span><span>Cache</span><span>Cached since</span><span>Last sync</span><span>State</span><span />
      </div>
      {DEVICES.map((d, i, arr) => (
        <div key={d.id} style={{ display: "grid", gridTemplateColumns: "minmax(180px, 1.2fr) minmax(160px, 1fr) minmax(180px, 1.2fr) 110px 130px 130px 110px 36px", gap: 14, padding: "13px 18px", alignItems: "center", borderBottom: i === arr.length - 1 ? "none" : "1px solid var(--border-soft)", fontSize: 13 }}>
          <span style={{ fontWeight: 500 }}>{d.who}</span>
          <span style={{ ...off_meta }}>{d.role}</span>
          <span style={{ ...off_meta }}>{d.device}</span>
          <span style={{ fontSize: 13, fontVariantNumeric: "tabular-nums" }}>{d.cacheSize}</span>
          <span style={{ ...off_meta }}>{d.cachedSince} · exp {d.expires}</span>
          <span style={{ ...off_meta }}>{d.lastSync}</span>
          <StatusChip status={d.state === "trusted" ? "confirmed" : "expired"} size="sm" label={d.state === "trusted" ? "Trusted" : "Blocked"} />
          <IconBtn icon={IconMore} size={28} />
        </div>
      ))}
    </Card>
    <div style={{ marginTop: 14, padding: 14, background: "var(--amber-100)", color: "var(--amber-600)", borderRadius: 12, display: "flex", gap: 14, alignItems: "center", fontSize: 12 }}>
      <IconShield size={16} />
      <div>
        <strong>One device blocked.</strong> Maya Okonkwo's iPhone hit 5 failed sync attempts in a row — usually a stale auth token. Trust auto-revoked. She'll be prompted to fully re-authenticate next time she opens the app.
      </div>
      <Btn size="sm" kind="secondary">Re-issue trust</Btn>
    </div>
  </>
);

// ---- POLICY ----
const PolicyTab = () => (
  <>
    <p style={{ fontSize: 13, color: "var(--fg-2)", marginBottom: 14, maxWidth: 620 }}>
      Tenant-wide policy for offline capture. Changes affect new sessions; existing trusted devices follow the policy they were issued under until they re-authenticate.
    </p>
    <Card style={{ marginBottom: 14 }}>
      <div style={{ ...off_over, marginBottom: 14 }}>What can be captured offline</div>
      <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
        <PolicyRow label="Volunteer self-log hours offline" desc="Volunteer captures hours on a phone with no signal. Sync on next online." on />
        <PolicyRow label="Staff attendance + no-show capture" desc="On-site lead marks present/no-show on day-of roster." on />
        <PolicyRow label="Staff hour adjustments offline" desc="On-site lead corrects volunteer hours in the field." on />
        <PolicyRow label="Outcome capture offline" desc="Trees planted, miles patrolled, etc. captured at the end of a shift." on />
        <PolicyRow label="Opportunity sign-up offline" desc="Volunteer signs up for a shift while offline. Off by default (capacity cannot be checked)." />
      </div>
    </Card>

    <Card style={{ marginBottom: 14 }}>
      <div style={{ ...off_over, marginBottom: 14 }}>Cache & trust</div>
      <Field label="Max cache age before re-auth">
        <Select defaultValue="7d" options={[{ value: "24h", label: "24 hours" }, { value: "3d", label: "3 days" }, { value: "7d", label: "7 days (default)" }, { value: "14d", label: "14 days" }]} />
      </Field>
      <Field label="Inactivity before silent re-lock">
        <Select defaultValue="30m" options={[{ value: "10m", label: "10 minutes" }, { value: "30m", label: "30 minutes (default)" }, { value: "60m", label: "60 minutes" }]} />
      </Field>
      <Field label="Block device after N failed syncs">
        <Select defaultValue="5" options={[{ value: "3", label: "3 attempts" }, { value: "5", label: "5 attempts (default)" }, { value: "10", label: "10 attempts" }]} />
      </Field>
      <Field label="Allowed device families">
        <div style={{ display: "flex", gap: 6, flexWrap: "wrap" }}>
          {["iOS 17+", "iPadOS 17+", "Android 12+", "Mobile web (Chromium)", "Mobile web (Safari)"].map(d => <span key={d} style={{ fontSize: 12, padding: "5px 11px", borderRadius: 999, background: "var(--sage-100)", color: "var(--sage-700)", fontWeight: 500 }}>{d}</span>)}
        </div>
      </Field>
    </Card>

    <Card>
      <div style={{ ...off_over, marginBottom: 14 }}>Conflict resolution defaults</div>
      <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
        <PolicyRow label="Newer wins (timestamp)" desc="Default for most conflicts. Both versions preserved in audit." on />
        <PolicyRow label="Coordinator wins over volunteer" desc="If a coordinator and a volunteer disagree on attendance, the coordinator's record wins automatically." on />
        <PolicyRow label="Always send to staff review for hour-record conflicts" desc="Don't auto-resolve hour disagreements — surface them." on />
      </div>
    </Card>
  </>
);

const PolicyRow = ({ label, desc, on }) => {
  const [v, setV] = useOff9(!!on);
  return (
    <div style={{ display: "grid", gridTemplateColumns: "1fr auto", gap: 14, padding: "10px 12px", border: "1px solid var(--border-soft)", borderRadius: 10, alignItems: "center" }}>
      <div>
        <div style={{ fontSize: 13, fontWeight: 500 }}>{label}</div>
        <div style={{ ...off_meta, marginTop: 3, lineHeight: 1.4 }}>{desc}</div>
      </div>
      <Toggle checked={v} onChange={setV} />
    </div>
  );
};

// ============================================================
Object.assign(window, { OrgOffline });
