"use client";

/**
 * Smartsheet Integration — `/admin/water-log/settings` section.
 *
 * Self-contained card that lets a brand admin configure the Smartsheet
 * integration for their Water Log entries:
 *
 *   1. Enable/disable the integration
 *   2. Smartsheet sheet URL or numeric sheet ID
 *   3. API token (password field; never sent back to the client after save)
 *   4. Sync frequency (realtime / every 15 minutes / hourly)
 *   5. Column mapping (entry_id, logged_at, headgate, measurement,
 *      unit, irrigator, notes)
 *   6. Test Connection button — fetches sheet metadata so the column
 *      dropdowns are populated
 *   7. Recent Activity panel — last 10 sync attempts (green ✓ / red ✗)
 *
 * Visual language matches the parent Water Log settings page
 * (cream + forest palette, "Field Almanac" style).
 *
 * Threading brandId:
 *   The card takes `brandId` as a prop per CLAUDE.md ("Brand ID
 *   Threading"). The current Water Log settings page still hardcodes
 *   TUXEDO_BRAND_ID; this card accepts it as a prop so the page can
 *   be lifted later without rewrites.
 */

import { useEffect, useReducer, useState, useCallback } from "react";
import {
  getSmartsheetConfig,
  saveSmartsheetConfig,
  testSmartsheetConnection,
  listSmartsheetSyncLog,
  enqueueSmartsheetBackfill,
  type EnqueueBackfillResult,
  type SmartsheetConfigResponse,
  type SmartsheetSyncLogEntry,
} from "@/actions/water-log/smartsheet";
import type {
  SmartsheetColumnMapping,
  SmartsheetFrequency,
} from "@/db/schema/water-log";
import { formatDateTime } from "@/lib/format-date";

const COLUMN_LABELS: Record<keyof SmartsheetColumnMapping, string> = {
  entry_id: "Entry ID (UUID, for dedup)",
  logged_at: "Logged at (timestamp)",
  headgate: "Headgate name",
  measurement: "Measurement",
  unit: "Unit (CFS, GPM, …)",
  irrigator: "Irrigator name",
  notes: "Notes (optional)",
};

const REQUIRED_COLUMNS: (keyof SmartsheetColumnMapping)[] = [
  "entry_id",
  "logged_at",
];

// IDs for the in-page section nav. Hoisted to module scope so the
// IntersectionObserver effect can list it once in its deps array.
const SECTION_IDS = [
  "smartsheet-enable",
  "smartsheet-connection",
  "smartsheet-frequency",
  "smartsheet-mapping",
  "smartsheet-backfill",
  "smartsheet-activity",
] as const;
type SectionId = (typeof SECTION_IDS)[number];

const FREQUENCY_LABELS: Record<SmartsheetFrequency, string> = {
  realtime: "Real-time (after every entry)",
  every_15_minutes: "Every 15 minutes",
  hourly: "Hourly",
};

type ColumnOption = { id: string; title: string; type: string };

type TestState =
  | { state: "idle" }
  | { state: "loading" }
  | {
      state: "ok";
      sheetName: string;
      columnCount: number;
      columns: ColumnOption[];
    }
  | { state: "error"; message: string };

type State = {
  loading: boolean;
  saving: boolean;
  message: { type: "success" | "error"; text: string } | null;
  // Persisted (saved) values — for displaying the masked token, etc.
  savedConfig: SmartsheetConfigResponse | null;
  // Editable form fields:
  enabled: boolean;
  sheetIdInput: string;
  tokenInput: string; // empty string = keep existing
  showToken: boolean;
  frequency: SmartsheetFrequency;
  mapping: SmartsheetColumnMapping;
  // Available sheet columns (loaded via Test Connection)
  availableColumns: ColumnOption[];
  // Test Connection state
  test: TestState;
  // Recent activity
  recentLog: SmartsheetSyncLogEntry[];
  loadingLog: boolean;
  /** Whether the setup-instructions modal is open. */
  setupModalOpen: boolean;
  /** Backfill UI state. */
  backfill: {
    running: boolean;
    range: "all" | "30d" | "7d";
    result: EnqueueBackfillResult | null;
  };
};

type Action =
  | { type: "LOAD_START" }
  | { type: "LOAD_DONE"; config: SmartsheetConfigResponse; log: SmartsheetSyncLogEntry[] }
  | { type: "SAVE_START" }
  | { type: "SAVE_OK"; config: SmartsheetConfigResponse }
  | { type: "SAVE_FAIL"; message: string }
  | { type: "SAVE_DONE" }
  | { type: "SET_ENABLED"; value: boolean }
  | { type: "SET_SHEET_ID"; value: string }
  | { type: "SET_TOKEN"; value: string }
  | { type: "TOGGLE_SHOW_TOKEN" }
  | { type: "SET_FREQUENCY"; value: SmartsheetFrequency }
  | { type: "SET_MAPPING_KEY"; key: keyof SmartsheetColumnMapping; value: string }
  | { type: "TEST_START" }
  | { type: "TEST_OK"; sheetName: string; columns: ColumnOption[] }
  | { type: "TEST_FAIL"; message: string }
  | { type: "CLEAR_MESSAGE" }
  | { type: "REFRESH_LOG"; log: SmartsheetSyncLogEntry[] }
  | { type: "OPEN_SETUP_MODAL" }
  | { type: "CLOSE_SETUP_MODAL" }
  | { type: "BACKFILL_START" }
  | { type: "BACKFILL_RANGE"; value: "all" | "30d" | "7d" }
  | { type: "BACKFILL_DONE"; result: EnqueueBackfillResult };

const EMPTY_MAPPING: SmartsheetColumnMapping = {
  entry_id: "",
  logged_at: "",
  headgate: "",
  measurement: "",
  unit: "",
  irrigator: "",
  notes: null,
};

const initialState: State = {
  loading: true,
  saving: false,
  message: null,
  savedConfig: null,
  enabled: false,
  sheetIdInput: "",
  tokenInput: "",
  showToken: false,
  frequency: "hourly",
  mapping: { ...EMPTY_MAPPING },
  availableColumns: [],
  test: { state: "idle" },
  recentLog: [],
  loadingLog: false,
  setupModalOpen: false,
  backfill: { running: false, range: "all", result: null },
};

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case "LOAD_START":
      return { ...state, loading: true };
    case "LOAD_DONE": {
      const c = action.config;
      return {
        ...state,
        loading: false,
        savedConfig: c,
        enabled: c.syncEnabled,
        sheetIdInput: c.sheetId ?? "",
        // tokenInput stays empty — we don't preload from saved.
        frequency: c.syncFrequency,
        mapping: c.columnMapping ?? { ...EMPTY_MAPPING },
        recentLog: action.log,
      };
    }
    case "SAVE_START":
      return { ...state, saving: true, message: null };
    case "SAVE_OK":
      return {
        ...state,
        saving: false,
        message: { type: "success", text: "Smartsheet settings saved" },
        savedConfig: action.config,
        // Clear token input on save (token is now stored server-side).
        tokenInput: "",
        showToken: false,
      };
    case "SAVE_FAIL":
      return {
        ...state,
        saving: false,
        message: { type: "error", text: action.message },
      };
    case "SAVE_DONE":
      return { ...state, saving: false };
    case "SET_ENABLED":
      return { ...state, enabled: action.value };
    case "SET_SHEET_ID":
      return { ...state, sheetIdInput: action.value };
    case "SET_TOKEN":
      return { ...state, tokenInput: action.value };
    case "TOGGLE_SHOW_TOKEN":
      return { ...state, showToken: !state.showToken };
    case "SET_FREQUENCY":
      return { ...state, frequency: action.value };
    case "SET_MAPPING_KEY":
      return {
        ...state,
        mapping: {
          ...state.mapping,
          [action.key]: action.value === "" ? null : action.value,
        } as SmartsheetColumnMapping,
      };
    case "TEST_START":
      return { ...state, test: { state: "loading" } };
    case "TEST_OK":
      return {
        ...state,
        test: {
          state: "ok",
          sheetName: action.sheetName,
          columnCount: action.columns.length,
          columns: action.columns,
        },
        availableColumns: action.columns,
      };
    case "TEST_FAIL":
      return { ...state, test: { state: "error", message: action.message } };
    case "CLEAR_MESSAGE":
      return { ...state, message: null };
    case "REFRESH_LOG":
      return { ...state, recentLog: action.log };
    case "OPEN_SETUP_MODAL":
      return { ...state, setupModalOpen: true };
    case "CLOSE_SETUP_MODAL":
      return { ...state, setupModalOpen: false };
    case "BACKFILL_START":
      return {
        ...state,
        backfill: { ...state.backfill, running: true },
      };
    case "BACKFILL_RANGE":
      return {
        ...state,
        backfill: { ...state.backfill, range: action.value },
      };
    case "BACKFILL_DONE":
      return {
        ...state,
        backfill: {
          ...state.backfill,
          running: false,
          result: action.result,
        },
      };
    default:
      return state;
  }
}

export default function SmartsheetIntegrationCard({
  brandId,
}: {
  brandId: string;
}) {
  const [state, dispatch] = useReducer(reducer, initialState);

  const loadAll = useCallback(async () => {
    dispatch({ type: "LOAD_START" });
    const [config, log] = await Promise.all([
      getSmartsheetConfig(brandId),
      listSmartsheetSyncLog(brandId, 10),
    ]);
    dispatch({ type: "LOAD_DONE", config, log });
  }, [brandId]);

  useEffect(() => {
    void loadAll();
  }, [loadAll]);

  // Close the setup modal on Escape. Listens only while the modal is open.
  useEffect(() => {
    if (!state.setupModalOpen) return;
    function onKey(e: KeyboardEvent) {
      if (e.key === "Escape") {
        dispatch({ type: "CLOSE_SETUP_MODAL" });
      }
    }
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [state.setupModalOpen]);

  // ── In-page section nav: which section is currently visible ────────────
  // The "active zone" is the top 20% of the viewport (under the sticky nav).
  // When a section enters that zone, it becomes active. We keep the previous
  // active section during the gap between sections so the highlight doesn't
  // blink off when scrolling between two adjacent cards.
  const [activeSection, setActiveSection] = useState<SectionId>(
    "smartsheet-enable",
  );

  useEffect(() => {
    const elements = SECTION_IDS.map((id) => document.getElementById(id)).filter(
      (el): el is HTMLElement => el !== null,
    );
    if (elements.length === 0) return;

    const visible = new Set<string>();
    let lastActive: SectionId | null = null;

    const observer = new IntersectionObserver(
      (entries) => {
        for (const entry of entries) {
          if (entry.isIntersecting) {
            visible.add(entry.target.id);
          } else {
            visible.delete(entry.target.id);
          }
        }
        // Among visible sections, prefer the earliest (topmost) in DOM order.
        const ordered = SECTION_IDS.filter((id) => visible.has(id));
        const nextActive = (ordered[0] ?? lastActive) as SectionId | null;
        if (nextActive && nextActive !== lastActive) {
          lastActive = nextActive;
          setActiveSection(nextActive);
        }
      },
      {
        // Top 20% of the viewport = the visible "reading zone" under the nav.
        rootMargin: "0px 0px -80% 0px",
        threshold: 0,
      },
    );

    for (const el of elements) observer.observe(el);
    return () => observer.disconnect();
  }, []);

  // Smooth scroll on pill click. `scroll-behavior: smooth` is set globally on
  // <html> and the prefers-reduced-motion media query in globals.css flips
  // it to `auto`, so we don't need a JS-side matchMedia check here.
  const handleSectionClick = useCallback(
    (e: React.MouseEvent<HTMLAnchorElement>, id: string) => {
      const el = document.getElementById(id);
      if (!el) return;
      e.preventDefault();
      el.scrollIntoView({ block: "start" });
      // Reflect the destination in the URL without triggering a jump.
      if (typeof window !== "undefined" && window.history) {
        window.history.replaceState(null, "", `#${id}`);
      }
    },
    [],
  );

  async function handleSave(e: React.FormEvent) {
    e.preventDefault();
    dispatch({ type: "SAVE_START" });
    const result = await saveSmartsheetConfig(brandId, {
      sheetId: state.sheetIdInput,
      token: state.tokenInput,
      syncEnabled: state.enabled,
      syncFrequency: state.frequency,
      columnMapping: state.mapping,
    });
    if (result.success && result.config) {
      dispatch({ type: "SAVE_OK", config: result.config });
      // Refresh log so any pending activity from this turn is visible.
      const log = await listSmartsheetSyncLog(brandId, 10);
      dispatch({ type: "REFRESH_LOG", log });
    } else {
      dispatch({ type: "SAVE_FAIL", message: result.error ?? "Save failed" });
    }
  }

  async function handleTest() {
    if (!state.sheetIdInput) {
      dispatch({ type: "TEST_FAIL", message: "Enter a Sheet URL or ID first" });
      return;
    }
    if (!state.tokenInput && !state.savedConfig?.hasToken) {
      dispatch({
        type: "TEST_FAIL",
        message: "Paste your API token to test, or save the config first",
      });
      return;
    }
    dispatch({ type: "TEST_START" });
    const result = await testSmartsheetConnection(
      brandId,
      state.sheetIdInput,
      state.tokenInput,
    );
    if (result.success) {
      dispatch({
        type: "TEST_OK",
        sheetName: result.sheetName,
        columns: result.columns,
      });
    } else {
      dispatch({ type: "TEST_FAIL", message: result.error });
    }
  }

  async function handleBackfill() {
    if (state.backfill.running) return;
    if (!state.savedConfig?.configured || !state.enabled) {
      // Surface as a notice rather than call into the action just to fail.
      dispatch({
        type: "BACKFILL_DONE",
        result: {
          success: false,
          error: "Save and enable your Smartsheet config first.",
          considered: 0,
          newlyQueued: 0,
          alreadyQueued: 0,
          drained: { synced: 0, skipped: 0, failed: 0, remaining: 0 },
        },
      });
      return;
    }
    const rangeLabel =
      state.backfill.range === "30d"
        ? "the last 30 days"
        : state.backfill.range === "7d"
          ? "the last 7 days"
          : "all time";
    if (
      !window.confirm(
        `Sync ${rangeLabel} of water log entries to Smartsheet? This is safe to re-run — already-pushed entries are deduped by Entry ID.`,
      )
    ) {
      return;
    }
    dispatch({ type: "BACKFILL_START" });
    const since =
      state.backfill.range === "30d"
        ? new Date(Date.now() - 30 * 86_400_000)
        : state.backfill.range === "7d"
          ? new Date(Date.now() - 7 * 86_400_000)
          : undefined;
    const result = await enqueueSmartsheetBackfill(brandId, { since });
    dispatch({ type: "BACKFILL_DONE", result });
    // Refresh the log so the drain results show up immediately.
    const log = await listSmartsheetSyncLog(brandId, 10);
    dispatch({ type: "REFRESH_LOG", log });
  }

  if (state.loading) {
    return (
      <Card title="Smartsheet Integration" subtitle="Loading…">
        <p className="text-sm text-[#5a5d5a]">Reading saved configuration.</p>
      </Card>
    );
  }

  const noTokenYet = !state.savedConfig?.hasToken && state.tokenInput.length === 0;

  return (
    <form onSubmit={handleSave} className="space-y-6">
      <SectionNav activeId={activeSection} onNavigate={handleSectionClick} />
      {state.message && (
        <div
          role={state.message.type === "error" ? "alert" : "status"}
          aria-live={state.message.type === "error" ? "assertive" : "polite"}
          className={`smartsheet-slide-down flex items-start gap-3 rounded-lg border px-4 py-3 text-[13.5px]
            ${
              state.message.type === "success"
                ? "border-[#1a4d2e]/25 bg-[#1a4d2e]/[0.05] text-[#143d24]"
                : "border-[#a4452b]/25 bg-[#a4452b]/[0.05] text-[#7a341f]"
            }`}
        >
          <span
            aria-hidden
            className={`mt-0.5 inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-full text-white ${
              state.message.type === "success" ? "bg-[#1a4d2e]" : "bg-[#a4452b]"
            }`}
          >
            {state.message.type === "success" ? (
              <svg viewBox="0 0 12 12" className="h-3 w-3">
                <path
                  d="M2 6.5L5 9.5L10 3.5"
                  stroke="currentColor"
                  strokeWidth="2"
                  fill="none"
                  strokeLinecap="round"
                  strokeLinejoin="round"
                />
              </svg>
            ) : (
              <svg viewBox="0 0 12 12" className="h-3 w-3">
                <path
                  d="M6 3V6.5M6 8.5V9"
                  stroke="currentColor"
                  strokeWidth="2"
                  strokeLinecap="round"
                />
              </svg>
            )}
          </span>
          <p className="font-medium leading-snug">{state.message.text}</p>
        </div>
      )}

      <Card
        id="smartsheet-enable"
        title="Smartsheet Integration"
        subtitle="Push every new water log entry to a Smartsheet sheet automatically."
      >
        <ToggleRow
          label="Enable"
          description="When disabled, no new rows are pushed. Existing rows in Smartsheet are untouched."
          value={state.enabled}
          onChange={(v) => dispatch({ type: "SET_ENABLED", value: v })}
        />
      </Card>

      <Card
        id="smartsheet-connection"
        title="Connection"
        subtitle="Smartsheet API token + target sheet."
        action={
          <button
            type="button"
            onClick={() => dispatch({ type: "OPEN_SETUP_MODAL" })}
            className="inline-flex min-h-[32px] items-center justify-center rounded-lg border border-[#d4d9d3] bg-white px-3.5 py-1.5
              text-[12.5px] font-semibold text-[#5a5d5a]
              motion-safe:transition-[background-color,border-color,color] motion-safe:duration-150
              hover:border-[#1a4d2e] hover:bg-[#f4f1e8] hover:text-[#1a4d2e]
              focus:outline-none focus-visible:ring-4 focus-visible:ring-[#1a4d2e]/20
              active:scale-[0.97] motion-safe:transition-transform motion-safe:ease-out"
          >
            Setup instructions
          </button>
        }
      >
        {/* Sheet */}
        <div className="space-y-1">
          <label
            htmlFor="smartsheet-sheet"
            className="block text-xs font-medium text-[#5a5d5a]"
          >
            Sheet URL or sheet ID
          </label>
          <input
            id="smartsheet-sheet"
            type="text"
            value={state.sheetIdInput}
            onChange={(e) =>
              dispatch({ type: "SET_SHEET_ID", value: e.target.value })
            }
            placeholder="https://app.smartsheet.com/sheets/AbCdEfGhIjKlMn  or  123456789012345"
            className="block min-h-[44px] w-full rounded-lg border border-[#d4d9d3] bg-white px-4 py-2.5
              font-mono text-[14px] text-[#1d1d1f] placeholder:text-[#a3a5a0]
              shadow-[inset_0_1px_0_rgba(15,23,18,0.02)]
              outline-none motion-safe:transition-[border-color,box-shadow] motion-safe:duration-150
              focus:border-[#1a4d2e] focus:ring-4 focus:ring-[#1a4d2e]/15"
            autoComplete="off"
          />
          <p className="text-xs text-[#8a8b88]">
            Paste the share URL from your browser address bar, or the
            numeric sheet ID. If a share URL doesn&apos;t work, use the
            numeric ID from <em>File → Properties</em> in Smartsheet.
          </p>
        </div>

        {/* Token */}
        <div className="mt-4 space-y-1">
          <label
            htmlFor="smartsheet-token"
            className="block text-xs font-medium text-[#5a5d5a]"
          >
            API access token
          </label>
          <div className="flex gap-2">
            <input
              id="smartsheet-token"
              type={state.showToken ? "text" : "password"}
              value={state.tokenInput}
              onChange={(e) =>
                dispatch({ type: "SET_TOKEN", value: e.target.value })
              }
              placeholder={
                state.savedConfig?.hasToken
                  ? `Leave blank to keep the saved token (${state.savedConfig.maskedToken ?? "••••"})`
                  : "Paste your Smartsheet API token"
              }
              className="min-h-[44px] flex-1 rounded-lg border border-[#d4d9d3] bg-white px-4 py-2.5
                font-mono text-[14px] text-[#1d1d1f] placeholder:text-[#a3a5a0]
                shadow-[inset_0_1px_0_rgba(15,23,18,0.02)]
                outline-none motion-safe:transition-[border-color,box-shadow] motion-safe:duration-150
                focus:border-[#1a4d2e] focus:ring-4 focus:ring-[#1a4d2e]/15"
              autoComplete="off"
            />
            <button
              type="button"
              onClick={() => dispatch({ type: "TOGGLE_SHOW_TOKEN" })}
              className="inline-flex min-h-[44px] items-center justify-center rounded-lg border border-[#d4d9d3] bg-white px-3
                text-[13px] font-medium text-[#5a5d5a]
                motion-safe:transition-colors motion-safe:duration-150
                hover:bg-[#f4f1e8] hover:text-[#1d1d1f]
                focus:outline-none focus-visible:ring-4 focus-visible:ring-[#1a4d2e]/20
                active:scale-[0.97] motion-safe:transition-transform motion-safe:ease-out"
              aria-label={state.showToken ? "Hide token" : "Show token"}
            >
              {state.showToken ? "Hide" : "Show"}
            </button>
          </div>
          <p className="text-xs text-[#8a8b88]">
            Generate a token at{" "}
            <a
              href="https://app.smartsheet.com/b/home"
              target="_blank"
              rel="noopener noreferrer"
              className="text-[#1a4d2e] underline underline-offset-2"
            >
              app.smartsheet.com
            </a>{" "}
            → <span className="font-mono">Account</span> →{" "}
            <span className="font-mono">Personal Settings</span> →{" "}
            <span className="font-mono">API Access</span>. The token is
            encrypted with AES-256-GCM at rest.
          </p>
        </div>

        {/* Test Connection */}
        <div className="mt-4 flex items-center gap-3">
          <button
            type="button"
            onClick={handleTest}
            disabled={state.test.state === "loading"}
            className="inline-flex min-h-[40px] items-center justify-center rounded-lg border border-[#1a4d2e] bg-white px-4 py-2
              text-[14px] font-semibold text-[#1a4d2e]
              motion-safe:transition-[background-color,color,transform] motion-safe:duration-150 motion-safe:ease-out
              hover:bg-[#1a4d2e] hover:text-white
              focus:outline-none focus-visible:ring-4 focus-visible:ring-[#1a4d2e]/25
              active:scale-[0.97]
              disabled:opacity-50 disabled:cursor-not-allowed"
          >
            {state.test.state === "loading" ? (
              <span className="inline-flex items-center gap-2">
                <span className="h-3.5 w-3.5 animate-spin rounded-full border-2 border-current border-r-transparent motion-reduce:animate-none" />
                Testing…
              </span>
            ) : (
              "Test Connection"
            )}
          </button>
          <TestResultPanel test={state.test} />
        </div>
      </Card>

      <Card
        id="smartsheet-frequency"
        title="Sync frequency"
        subtitle="How often new water log entries are pushed to Smartsheet."
      >
        <div className="space-y-2">
          {(Object.keys(FREQUENCY_LABELS) as SmartsheetFrequency[]).map((f) => (
            <label
              key={f}
              className={`flex cursor-pointer items-start gap-3 rounded-lg border px-4 py-3
                motion-safe:transition-[border-color,background-color] motion-safe:duration-150
                ${
                  state.frequency === f
                    ? "border-[#1a4d2e] bg-[#1a4d2e]/[0.04]"
                    : "border-[#d4d9d3] bg-white hover:border-[#a8b4a4] hover:bg-[#fcfaf3]"
                }
                focus-within:ring-4 focus-within:ring-[#1a4d2e]/15 focus-within:border-[#1a4d2e]
                active:scale-[0.997] motion-safe:transition-transform motion-safe:ease-out`}
            >
              <input
                type="radio"
                name="smartsheet-frequency"
                value={f}
                checked={state.frequency === f}
                onChange={() =>
                  dispatch({ type: "SET_FREQUENCY", value: f })
                }
                className="mt-0.5 h-4 w-4 cursor-pointer accent-[#1a4d2e]"
              />
              <span className="flex-1 text-[14px] font-medium text-[#1d1d1f]">
                {FREQUENCY_LABELS[f]}
              </span>
            </label>
          ))}
        </div>
      </Card>

      <Card
        id="smartsheet-mapping"
        title="Column mapping"
        subtitle="Map our water log fields to columns in your Smartsheet. entry_id and logged_at are required."
      >
        {state.availableColumns.length === 0 ? (
          <p className="text-xs text-[#8a8b88]">
            Run <strong>Test Connection</strong> above to load the sheet&apos;s
            columns. You can also paste column IDs manually below.
          </p>
        ) : (
          <p className="text-xs text-[#8a8b88]">
            Loaded {state.availableColumns.length} columns from sheet{" "}
            <span className="font-mono">
              {state.test.state === "ok" ? state.test.sheetName : "—"}
            </span>
            . Pick from the dropdowns below.
          </p>
        )}

        <div className="mt-4 space-y-3">
          {(Object.keys(COLUMN_LABELS) as Array<keyof SmartsheetColumnMapping>).map(
            (key) => {
              const required = REQUIRED_COLUMNS.includes(key);
              const value = state.mapping[key];
              const valueAsString = value ?? "";
              return (
                <div key={key} className="flex items-center gap-3">
                  <label
                    htmlFor={`map-${key}`}
                    className="w-1/3 text-sm font-medium text-[#1d1d1f]"
                  >
                    {COLUMN_LABELS[key]}
                    {required && (
                      <span className="ml-1 text-xs text-[#a4452b]">
                        required
                      </span>
                    )}
                  </label>
                  {state.availableColumns.length > 0 ? (
                    <select
                      id={`map-${key}`}
                      value={valueAsString}
                      onChange={(e) =>
                        dispatch({
                          type: "SET_MAPPING_KEY",
                          key,
                          value: e.target.value,
                        })
                      }
                      className="min-h-[40px] flex-1 rounded-lg border border-[#d4d9d3] bg-white px-3 py-2
                        text-[14px] text-[#1d1d1f]
                        outline-none motion-safe:transition-[border-color,box-shadow] motion-safe:duration-150
                        focus:border-[#1a4d2e] focus:ring-4 focus:ring-[#1a4d2e]/15"
                    >
                      <option value="">
                        — {required ? "Select a column" : "Not mapped"} —
                      </option>
                      {state.availableColumns.map((c) => (
                        <option key={c.id} value={c.id}>
                          {c.title}
                          {c.type !== "TEXT_NUMBER"
                            ? ` (${c.type.toLowerCase()})`
                            : ""}
                        </option>
                      ))}
                    </select>
                  ) : (
                    <input
                      id={`map-${key}`}
                      type="text"
                      value={valueAsString}
                      onChange={(e) =>
                        dispatch({
                          type: "SET_MAPPING_KEY",
                          key,
                          value: e.target.value,
                        })
                      }
                      placeholder="Column ID (numeric)"
                      className="min-h-[40px] flex-1 rounded-lg border border-[#d4d9d3] bg-white px-3 py-2
                        font-mono text-[14px] text-[#1d1d1f] placeholder:text-[#a3a5a0]
                        outline-none motion-safe:transition-[border-color,box-shadow] motion-safe:duration-150
                        focus:border-[#1a4d2e] focus:ring-4 focus:ring-[#1a4d2e]/15"
                    />
                  )}
                </div>
              );
            },
          )}
        </div>
      </Card>

      <BackfillCard
        id="smartsheet-backfill"
        state={state.backfill}
        onRun={handleBackfill}
        onRangeChange={(v) => dispatch({ type: "BACKFILL_RANGE", value: v })}
      />

      <Card
        id="smartsheet-activity"
        title="Recent activity"
        subtitle="Last 10 sync attempts. Use this to spot token / sheet issues quickly."
      >
        {state.loadingLog ? (
          <p className="text-sm text-[#5a5d5a]">Loading…</p>
        ) : state.recentLog.length === 0 ? (
          <p className="text-sm text-[#5a5d5a]">
            No sync attempts yet. Save the config and submit a water log
            entry from <code className="rounded bg-[#f4f1e8] px-1 py-0.5 font-mono text-xs">/water</code>.
          </p>
        ) : (
          <ul className="divide-y divide-[#d4d9d3]">
            {state.recentLog.map((log) => (
              <li
                key={log.id}
                className="flex items-start gap-3 py-3 first:pt-0 last:pb-0"
              >
                <span
                  className={`mt-0.5 inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-full text-xs font-bold text-white ${
                    log.success ? "bg-[#1a4d2e]" : "bg-[#a4452b]"
                  }`}
                  aria-label={log.success ? "success" : "failure"}
                >
                  {log.success ? "✓" : "✗"}
                </span>
                <div className="min-w-0 flex-1">
                  <div className="flex flex-wrap items-baseline gap-x-3 gap-y-1 text-xs">
                    <span className="font-mono font-semibold text-[#1d1d1f]">
                      {log.action}
                    </span>
                    <span className="text-[#5a5d5a]">
                      {formatDateTime(log.createdAt)}
                    </span>
                    {log.durationMs != null && (
                      <span className="text-[#8a8b88]">
                        {log.durationMs}ms
                      </span>
                    )}
                  </div>
                  {log.error && (
                    <p className="mt-0.5 text-xs text-[#a4452b]">{log.error}</p>
                  )}
                </div>
              </li>
            ))}
          </ul>
        )}
      </Card>

      <button
        type="submit"
        disabled={state.saving || noTokenYet}
        className="inline-flex min-h-[48px] w-full items-center justify-center rounded-lg bg-[#1a4d2e] px-6 py-3
          text-[14px] font-semibold tracking-tight text-white
          shadow-[0_1px_0_rgba(0,0,0,0.06),inset_0_1px_0_rgba(255,255,255,0.06)]
          motion-safe:transition-[background-color,transform,box-shadow] motion-safe:duration-150 motion-safe:ease-out
          hover:bg-[#143d24] hover:shadow-[0_2px_4px_rgba(15,23,18,0.1),inset_0_1px_0_rgba(255,255,255,0.06)]
          focus:outline-none focus-visible:ring-4 focus-visible:ring-[#1a4d2e]/30 focus-visible:ring-offset-2 focus-visible:ring-offset-[#fdfaf2]
          active:scale-[0.985] active:bg-[#0f2e1c]
          disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:bg-[#1a4d2e]"
      >
        {state.saving ? (
          <span className="inline-flex items-center gap-2">
            <span className="h-4 w-4 animate-spin rounded-full border-2 border-white/30 border-t-white motion-reduce:animate-none" />
            Saving…
          </span>
        ) : (
          "Save changes"
        )}
      </button>

      {noTokenYet && (
        <p className="text-center text-xs text-[#8a8b88]">
          Paste an API token above to enable Save.
        </p>
      )}

      {state.setupModalOpen && (
        <SetupInstructionsModal
          onClose={() => dispatch({ type: "CLOSE_SETUP_MODAL" })}
        />
      )}
    </form>
  );
}

// ── Subcomponents (mirror the parent settings page style) ──────────────────

function SectionNav({
  activeId,
  onNavigate,
}: {
  activeId: string;
  onNavigate: (e: React.MouseEvent<HTMLAnchorElement>, id: string) => void;
}) {
  const sections: Array<{ id: string; label: string }> = [
    { id: "smartsheet-enable", label: "Enable" },
    { id: "smartsheet-connection", label: "Connection" },
    { id: "smartsheet-frequency", label: "Frequency" },
    { id: "smartsheet-mapping", label: "Mapping" },
    { id: "smartsheet-backfill", label: "Backfill" },
    { id: "smartsheet-activity", label: "Activity" },
  ];

  return (
    <nav
      aria-label="On this page"
      className="smartsheet-fade-in sticky top-2 z-20 -mx-1 flex flex-wrap items-center gap-1 rounded-xl border border-[#d4d9d3] bg-[#fdfaf2]/92 px-3 py-2 shadow-[0_1px_2px_rgba(15,23,18,0.04),0_2px_6px_-2px_rgba(15,23,18,0.06)] backdrop-blur-md"
    >
      <span className="mr-1 hidden text-[10px] font-mono uppercase tracking-[0.22em] text-[#8a6b3b] sm:inline">
        On this page
      </span>
      <ul className="flex flex-wrap items-center gap-1">
        {sections.map((s) => {
          const active = activeId === s.id;
          return (
            <li key={s.id}>
              <a
                href={`#${s.id}`}
                aria-current={active ? "true" : undefined}
                onClick={(e) => onNavigate(e, s.id)}
                className={`inline-flex min-h-[28px] items-center justify-center rounded-full px-2.5 text-[12px] font-semibold tracking-tight
                  motion-safe:transition-[background-color,color,transform,box-shadow] motion-safe:duration-150 motion-safe:ease-out
                  focus:outline-none focus-visible:ring-4 focus-visible:ring-[#1a4d2e]/25
                  active:scale-[0.96]
                  ${
                    active
                      ? "bg-[#1a4d2e] text-white shadow-[0_1px_2px_rgba(15,23,18,0.1)]"
                      : "text-[#5a5d5a] hover:bg-[#f0ebde] hover:text-[#1d1d1f]"
                  }`}
              >
                {s.label}
              </a>
            </li>
          );
        })}
      </ul>
    </nav>
  );
}

function Card({
  id,
  title,
  subtitle,
  action,
  children,
}: {
  id?: string;
  title: string;
  subtitle?: string;
  action?: React.ReactNode;
  children: React.ReactNode;
}) {
  return (
    <section
      id={id}
      // `scroll-mt-20` keeps anchor jumps clear of the sticky "On this page" nav.
      className="scroll-mt-20 rounded-xl border border-[#d4d9d3] bg-white/95 p-6 shadow-[0_1px_2px_rgba(15,23,18,0.04),0_2px_6px_-2px_rgba(15,23,18,0.06)] motion-safe:transition-shadow"
    >
      <header className="mb-5 flex items-start justify-between gap-4">
        <div className="min-w-0 flex-1">
          <h2 className="text-[15px] font-semibold tracking-tight text-[#1d1d1f]">
            {title}
          </h2>
          {subtitle && (
            <p className="mt-1 text-[13px] leading-snug text-[#5a5d5a]">
              {subtitle}
            </p>
          )}
        </div>
        {action && <div className="shrink-0">{action}</div>}
      </header>
      {children}
    </section>
  );
}

function ToggleRow({
  label,
  description,
  value,
  onChange,
}: {
  label: string;
  description?: string;
  value: boolean;
  onChange: (v: boolean) => void;
}) {
  return (
    <div className="flex items-center justify-between gap-4 py-1">
      <div className="min-w-0 flex-1">
        <p className="text-[14px] font-medium leading-snug text-[#1d1d1f]">
          {label}
        </p>
        {description && (
          <p className="mt-0.5 text-[12.5px] leading-snug text-[#5a5d5a]">
            {description}
          </p>
        )}
      </div>
      <button
        type="button"
        role="switch"
        aria-checked={value}
        aria-label={label}
        onClick={() => onChange(!value)}
        className={`group relative inline-flex h-[26px] w-[46px] shrink-0 items-center rounded-full
          motion-safe:transition-colors motion-safe:duration-200
          focus:outline-none focus-visible:ring-4 focus-visible:ring-[#1a4d2e]/25 focus-visible:ring-offset-2 focus-visible:ring-offset-white
          active:scale-[0.97] motion-safe:transition-transform motion-safe:ease-out
          ${value ? "bg-[#1a4d2e]" : "bg-[#d4d9d3]"}`}
      >
        <span
          className={`inline-block h-[20px] w-[20px] transform rounded-full bg-white shadow-[0_1px_2px_rgba(0,0,0,0.2),0_1px_1px_rgba(0,0,0,0.06)]
            motion-safe:transition-transform motion-safe:duration-200
            motion-safe:ease-[cubic-bezier(0.32,0.72,0,1)]
            ${value ? "translate-x-[22px]" : "translate-x-[3px]"}`}
        />
      </button>
    </div>
  );
}

function TestResultPanel({ test }: { test: TestState }) {
  if (test.state === "idle") return null;
  if (test.state === "loading") {
    return (
      <span className="inline-flex items-center gap-2 text-[13px] text-[#5a5d5a]">
        <span className="h-3.5 w-3.5 animate-spin rounded-full border-2 border-[#d4d9d3] border-t-[#1a4d2e] motion-reduce:animate-none" />
        Testing connection…
      </span>
    );
  }
  if (test.state === "ok") {
    return (
      <div className="flex flex-1 items-start gap-2 rounded-lg border border-[#1a4d2e]/25 bg-[#1a4d2e]/[0.04] px-3 py-2 text-[13px]">
        <span className="mt-0.5 inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-full bg-[#1a4d2e] text-white">
          <svg viewBox="0 0 12 12" className="h-2.5 w-2.5" aria-hidden>
            <path
              d="M2 6.5L5 9.5L10 3.5"
              stroke="currentColor"
              strokeWidth="2"
              fill="none"
              strokeLinecap="round"
              strokeLinejoin="round"
            />
          </svg>
        </span>
        <p className="text-[#1a4d2e]">
          Connected to <span className="font-semibold">&ldquo;{test.sheetName}&rdquo;</span>{" "}
          <span className="text-[#3a6649]">— {test.columnCount} columns loaded</span>
        </p>
      </div>
    );
  }
  return (
    <div className="flex flex-1 items-start gap-2 rounded-lg border border-[#a4452b]/25 bg-[#a4452b]/[0.04] px-3 py-2 text-[13px]">
      <span className="mt-0.5 inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-full bg-[#a4452b] text-white">
        <svg viewBox="0 0 12 12" className="h-2.5 w-2.5" aria-hidden>
          <path
            d="M3 3L9 9M9 3L3 9"
            stroke="currentColor"
            strokeWidth="2"
            strokeLinecap="round"
          />
        </svg>
      </span>
      <p className="text-[#a4452b]">{test.message}</p>
    </div>
  );
}

// ── Backfill card ──────────────────────────────────────────────────────────

type BackfillState = {
  running: boolean;
  range: "all" | "30d" | "7d";
  result: EnqueueBackfillResult | null;
};

function BackfillCard({
  id,
  state,
  onRun,
  onRangeChange,
}: {
  id?: string;
  state: BackfillState;
  onRun: () => void;
  onRangeChange: (value: "all" | "30d" | "7d") => void;
}) {
  const result = state.result;
  // Success block — counts from the inline drain that ran after enqueue.
  const showResult = result && result.success;
  const showError = result && !result.success;

  return (
    <Card
      id={id}
      title="Backfill historical entries"
      subtitle="Push existing water log entries to your Smartsheet. Safe to re-run — already-pushed entries are deduped by Entry ID."
    >
      <div className="space-y-3">
        <div className="flex flex-wrap items-center gap-3">
          <label
            htmlFor="smartsheet-backfill-range"
            className="text-sm font-medium text-[#1d1d1f]"
          >
            Range
          </label>
          <select
            id="smartsheet-backfill-range"
            value={state.range}
            onChange={(e) =>
              onRangeChange(e.target.value as "all" | "30d" | "7d")
            }
            className="min-h-[40px] rounded-lg border border-[#d4d9d3] bg-white px-3 py-2
              text-[14px] text-[#1d1d1f]
              outline-none motion-safe:transition-[border-color,box-shadow] motion-safe:duration-150
              focus:border-[#1a4d2e] focus:ring-4 focus:ring-[#1a4d2e]/15
              disabled:opacity-50"
            disabled={state.running}
          >
            <option value="all">All time</option>
            <option value="30d">Last 30 days</option>
            <option value="7d">Last 7 days</option>
          </select>
          <button
            type="button"
            onClick={onRun}
            disabled={state.running}
            className="inline-flex min-h-[40px] items-center justify-center rounded-lg border border-[#1a4d2e] bg-white px-4 py-2
              text-[14px] font-semibold text-[#1a4d2e]
              motion-safe:transition-[background-color,color,transform] motion-safe:duration-150 motion-safe:ease-out
              hover:bg-[#1a4d2e] hover:text-white
              focus:outline-none focus-visible:ring-4 focus-visible:ring-[#1a4d2e]/25
              active:scale-[0.97]
              disabled:opacity-50 disabled:cursor-not-allowed"
          >
            {state.running ? (
              <span className="inline-flex items-center gap-2">
                <span className="h-3.5 w-3.5 animate-spin rounded-full border-2 border-current border-r-transparent motion-reduce:animate-none" />
                Running backfill…
              </span>
            ) : (
              "Run backfill"
            )}
          </button>
        </div>

        {state.running && (
          <p className="text-xs text-[#5a5d5a]">
            Counting entries, enqueueing, and draining one batch inline. Any
            remaining entries will be drained by the cron (every 15 minutes).
          </p>
        )}

        {showError && (
          <div
            role="alert"
            aria-live="assertive"
            className="flex items-start gap-2.5 rounded-lg border border-[#a4452b]/25 bg-[#a4452b]/[0.05] px-3 py-2.5 text-[13px]"
          >
            <span className="mt-0.5 inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-full bg-[#a4452b] text-white">
              <svg viewBox="0 0 12 12" className="h-2.5 w-2.5">
                <path
                  d="M3 3L9 9M9 3L3 9"
                  stroke="currentColor"
                  strokeWidth="2"
                  strokeLinecap="round"
                />
              </svg>
            </span>
            <p className="font-medium leading-snug text-[#7a341f]">
              {result.error}
            </p>
          </div>
        )}

        {showResult && result && (
          <div
            role="status"
            aria-live="polite"
            className="flex items-start gap-2.5 rounded-lg border border-[#1a4d2e]/25 bg-[#1a4d2e]/[0.04] px-3 py-2.5 text-[13px]"
          >
            <span className="mt-0.5 inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-full bg-[#1a4d2e] text-white">
              <svg viewBox="0 0 12 12" className="h-2.5 w-2.5">
                <path
                  d="M2 6.5L5 9.5L10 3.5"
                  stroke="currentColor"
                  strokeWidth="2"
                  fill="none"
                  strokeLinecap="round"
                  strokeLinejoin="round"
                />
              </svg>
            </span>
            <div className="text-[#143d24]">
              <p className="font-medium leading-snug">
                Found {result.considered}{" "}
                {result.considered === 1 ? "entry" : "entries"} matching the
                range
                {result.considered > 0 && (
                  <>
                    {" "}
                    ({result.newlyQueued} new, {result.alreadyQueued} already
                    queued).
                  </>
                )}
              </p>
              {(result.drained.synced +
                result.drained.skipped +
                result.drained.failed >
                0 || result.drained.remaining > 0) && (
                <p className="mt-1 leading-snug text-[#1a4d2e]/85">
                  Inline drain: {result.drained.synced} synced /{" "}
                  {result.drained.skipped} skipped / {result.drained.failed}{" "}
                  failed.
                  {result.drained.remaining > 0 && (
                    <>
                      {" "}
                      <strong className="font-semibold text-[#1a4d2e]">
                        {result.drained.remaining} still queued
                      </strong>{" "}
                      — they&apos;ll drain on the next cron tick (within 15
                      minutes).
                    </>
                  )}
                  {result.drained.remaining === 0 &&
                    result.considered > 0 && (
                      <> All entries are now in Smartsheet.</>
                    )}
                </p>
              )}
            </div>
          </div>
        )}
      </div>
    </Card>
  );
}

// ── Setup instructions modal ───────────────────────────────────────────────

const FIELD_SCHEMA: Array<{
  key: keyof SmartsheetColumnMapping;
  label: string;
  required: boolean;
  type: string;
  example: string;
  description: string;
}> = [
  {
    key: "entry_id",
    label: "Entry ID",
    required: true,
    type: "TEXT_NUMBER",
    example: "550e8400-e29b-41d4-a716-446655440000",
    description:
      "Unique UUID for each water log row. We use this for dedup — if the same Entry ID is already in your sheet, we skip the insert instead of creating a duplicate row.",
  },
  {
    key: "logged_at",
    label: "Logged At",
    required: true,
    type: "DATETIME (or DATE)",
    example: "2026-07-03 14:50",
    description:
      "When the reading was taken. Use a DATETIME column for full timestamp, or DATE for day-only. We format automatically based on the column type.",
  },
  {
    key: "headgate",
    label: "Headgate",
    required: false,
    type: "TEXT_NUMBER",
    example: "Section 4 — North Field",
    description: "Display name of the headgate the reading came from.",
  },
  {
    key: "measurement",
    label: "Measurement",
    required: false,
    type: "TEXT_NUMBER",
    example: "123.45",
    description:
      "The numeric reading. Sent as a string (e.g. \"123.45\"); Smartsheet's TEXT_NUMBER columns accept this and display it as a number.",
  },
  {
    key: "unit",
    label: "Unit",
    required: false,
    type: "TEXT_NUMBER",
    example: "CFS",
    description: "Display unit, e.g. CFS, GPM, AF/Day, inches.",
  },
  {
    key: "irrigator",
    label: "Irrigator",
    required: false,
    type: "TEXT_NUMBER",
    example: "Jane Doe",
    description: "Name of the irrigator who logged the reading.",
  },
  {
    key: "notes",
    label: "Notes",
    required: false,
    type: "TEXT_NUMBER",
    example: "Replaced meter seal",
    description: "Free-text notes attached to the entry (may be empty).",
  },
];

function SetupInstructionsModal({ onClose }: { onClose: () => void }) {
  return (
    <div
      className="smartsheet-fade-in fixed inset-0 z-50 flex items-end justify-center bg-black/40 p-0 backdrop-blur-[6px]
        sm:items-center sm:p-4"
      role="dialog"
      aria-modal="true"
      aria-labelledby="smartsheet-setup-title"
      onClick={(e) => {
        // Close when clicking the backdrop (but not the panel itself).
        if (e.target === e.currentTarget) onClose();
      }}
    >
      <div
        className="relative max-h-[92vh] w-full max-w-2xl overflow-y-auto rounded-t-2xl border border-[#d4d9d3]
          bg-[#fdfaf2] shadow-[0_-12px_40px_-12px_rgba(15,23,18,0.2),0_24px_60px_-20px_rgba(15,23,18,0.25)]
          smartsheet-sheet-up
          sm:rounded-2xl"
      >
        {/* Header */}
        <header className="sticky top-0 z-10 flex items-start justify-between gap-4 border-b border-[#d4d9d3]/80 bg-[#fdfaf2]/95 px-6 py-4 backdrop-blur-sm">
          <div>
            <p className="text-[10px] font-mono uppercase tracking-[0.3em] text-[#8a6b3b]">
              § 05.1 — Setup
            </p>
            <h2
              id="smartsheet-setup-title"
              className="mt-1 text-[22px] font-medium leading-tight tracking-tight text-[#1a4d2e]"
              style={{ fontFamily: "var(--font-display, Georgia, serif)" }}
            >
              Connect a Smartsheet
            </h2>
          </div>
          <button
            type="button"
            onClick={onClose}
            aria-label="Close"
            className="-mr-2 -mt-1 inline-flex h-9 w-9 items-center justify-center rounded-full
              text-[#5a5d5a]
              motion-safe:transition-colors motion-safe:duration-150
              hover:bg-[#f0ebde] hover:text-[#1d1d1f]
              focus:outline-none focus-visible:ring-4 focus-visible:ring-[#1a4d2e]/25
              active:scale-95 motion-safe:transition-transform motion-safe:ease-out"
          >
            <svg
              xmlns="http://www.w3.org/2000/svg"
              viewBox="0 0 20 20"
              fill="currentColor"
              className="h-5 w-5"
              aria-hidden
            >
              <path
                fillRule="evenodd"
                d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
                clipRule="evenodd"
              />
            </svg>
          </button>
        </header>

        <div className="space-y-6 px-6 py-5">
          {/* Prerequisites */}
          <section>
            <h3 className="text-sm font-semibold text-[#1d1d1f]">
              Before you start
            </h3>
            <ul className="mt-2 list-inside list-disc space-y-1 text-sm text-[#5a5d5a]">
              <li>You need a Smartsheet account.</li>
              <li>
                The target sheet must exist in your account. We can add
                columns to it for you (see Step 2), but we cannot create
                the sheet itself.
              </li>
              <li>
                You need admin access to the Water Log settings page
                (you&apos;re already here).
              </li>
            </ul>
          </section>

          {/* Step 1 */}
          <section>
            <h3 className="flex items-baseline gap-2 text-sm font-semibold text-[#1d1d1f]">
              <span className="inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-[#1a4d2e] text-xs font-bold text-white">
                1
              </span>
              Generate an API token
            </h3>
            <ol className="mt-2 list-inside list-decimal space-y-1 text-sm text-[#5a5d5a]">
              <li>
                Go to{" "}
                <a
                  href="https://app.smartsheet.com/b/home"
                  target="_blank"
                  rel="noopener noreferrer"
                  className="text-[#1a4d2e] underline underline-offset-2"
                >
                  app.smartsheet.com
                </a>
                .
              </li>
              <li>
                Click your avatar (top right) →{" "}
                <span className="font-mono text-xs">Account</span> →{" "}
                <span className="font-mono text-xs">Personal Settings</span>{" "}
                → <span className="font-mono text-xs">API Access</span>.
              </li>
              <li>
                Click <strong>Generate new access token</strong>. Give it a
                name like <span className="font-mono text-xs">RouteCommerce Water Log</span>.
              </li>
              <li>
                Copy the token <em>immediately</em> — Smartsheet only shows
                it once.
              </li>
            </ol>
          </section>

          {/* Step 2 */}
          <section>
            <h3 className="flex items-baseline gap-2 text-sm font-semibold text-[#1d1d1f]">
              <span className="inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-[#1a4d2e] text-xs font-bold text-white">
                2
              </span>
              Set up your sheet
            </h3>
            <p className="mt-2 text-sm text-[#5a5d5a]">
              Your sheet needs at least the two <strong>required</strong>{" "}
              columns below. The other five are optional but recommended —
              you decide which of your sheet&apos;s existing columns
              receives each field.
            </p>

            <div className="mt-3 overflow-hidden rounded-lg border border-[#d4d9d3]">
              <table className="w-full text-xs">
                <thead className="bg-[#f4f1e8] text-[#5a5d5a]">
                  <tr>
                    <th className="px-3 py-2 text-left font-semibold">
                      Our field
                    </th>
                    <th className="px-3 py-2 text-left font-semibold">
                      Smartsheet column type
                    </th>
                    <th className="px-3 py-2 text-left font-semibold">
                      Example
                    </th>
                  </tr>
                </thead>
                <tbody className="divide-y divide-[#d4d9d3] bg-white">
                  {FIELD_SCHEMA.map((f) => (
                    <tr key={f.key}>
                      <td className="px-3 py-2 align-top">
                        <div className="font-semibold text-[#1d1d1f]">
                          {f.label}
                          {f.required && (
                            <span className="ml-1 text-[#a4452b]">*</span>
                          )}
                        </div>
                        <div className="mt-0.5 text-[10px] text-[#8a8b88]">
                          {f.description}
                        </div>
                      </td>
                      <td className="px-3 py-2 align-top font-mono text-[11px] text-[#5a5d5a]">
                        {f.type}
                      </td>
                      <td className="px-3 py-2 align-top font-mono text-[11px] text-[#5a5d5a]">
                        {f.example}
                      </td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
            <p className="mt-2 text-xs text-[#8a8b88]">
              <span className="text-[#a4452b]">*</span> Required. The
              Entry ID column is what we use for dedup — without it, a
              re-sync would create duplicate rows.
            </p>

            <details className="mt-3 rounded-lg border border-[#d4d9d3] bg-white">
              <summary className="cursor-pointer px-4 py-2 text-sm font-medium text-[#1a4d2e] hover:bg-[#f4f1e8]">
                Copy a starter header row
              </summary>
              <pre className="overflow-x-auto border-t border-[#d4d9d3] bg-[#fdfaf2] px-4 py-3 text-xs text-[#1d1d1f]">
                <code>
                  Entry ID,Logged At,Headgate,Measurement,Unit,Irrigator,Notes
                </code>
              </pre>
              <p className="border-t border-[#d4d9d3] px-4 py-2 text-xs text-[#5a5d5a]">
                Paste this into row 1 of a new sheet, then click the
                <span className="font-mono"> ▼ </span>
                on the Entry ID cell and choose{" "}
                <span className="font-mono">Column Type → Text/Number</span>.
                Repeat for the others.
              </p>
            </details>
          </section>

          {/* Step 3 */}
          <section>
            <h3 className="flex items-baseline gap-2 text-sm font-semibold text-[#1d1d1f]">
              <span className="inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-[#1a4d2e] text-xs font-bold text-white">
                3
              </span>
              Connect
            </h3>
            <ol className="mt-2 list-inside list-decimal space-y-1 text-sm text-[#5a5d5a]">
              <li>
                Close this dialog and paste your <strong>sheet URL</strong>{" "}
                or numeric sheet ID into the Sheet URL field. (File →
                Properties in Smartsheet shows the numeric ID.)
              </li>
              <li>
                Paste your API token into the API Access Token field.
              </li>
              <li>
                Click <strong>Test Connection</strong> — we&apos;ll load
                your sheet&apos;s columns into the dropdowns.
              </li>
              <li>
                Map each of our fields to one of your sheet&apos;s
                columns. Unmapped fields are simply skipped.
              </li>
              <li>Click <strong>Save Settings</strong>.</li>
            </ol>
            <p className="mt-3 rounded-lg border border-[#1a4d2e]/30 bg-[#1a4d2e]/5 px-3 py-2 text-xs text-[#1a4d2e]">
              <strong>Tip:</strong> the first sync runs immediately if you
              set frequency to <em>Real-time</em>, or on the next cron
              tick for 15-minute / hourly. Submit a test entry from{" "}
              <code className="font-mono">/water</code> to see it land in
              your sheet.
            </p>
          </section>

          {/* Troubleshooting */}
          <section className="border-t border-[#d4d9d3] pt-4">
            <h3 className="text-sm font-semibold text-[#1d1d1f]">
              Common issues
            </h3>
            <ul className="mt-2 space-y-2 text-xs text-[#5a5d5a]">
              <li>
                <strong>401 Unauthorized</strong> — the token is wrong or
                expired. Re-generate it in Smartsheet.
              </li>
              <li>
                <strong>404 Sheet not found</strong> — if you pasted a
                share URL with a slug (e.g. <code className="font-mono">/sheets/abc123</code>),
                Smartsheet sometimes 404s on those. Use the numeric sheet
                ID instead, found in{" "}
                <span className="font-mono">File → Properties</span>.
              </li>
              <li>
                <strong>Test Connection succeeds but rows don&apos;t
                appear</strong> — the column type probably rejects our
                value (most common: a DATE column getting an ISO
                timestamp). Check the Recent Activity panel for the exact
                error.
              </li>
            </ul>
          </section>
        </div>

        {/* Footer */}
        <footer className="sticky bottom-0 flex justify-end gap-2 border-t border-[#d4d9d3]/80 bg-[#fdfaf2]/95 px-6 py-3 backdrop-blur-sm">
          <button
            type="button"
            onClick={onClose}
            className="inline-flex min-h-[40px] items-center justify-center rounded-lg bg-[#1a4d2e] px-5
              text-[14px] font-semibold tracking-tight text-white
              shadow-[0_1px_0_rgba(0,0,0,0.06),inset_0_1px_0_rgba(255,255,255,0.06)]
              motion-safe:transition-[background-color,transform] motion-safe:duration-150 motion-safe:ease-out
              hover:bg-[#143d24]
              focus:outline-none focus-visible:ring-4 focus-visible:ring-[#1a4d2e]/30
              active:scale-[0.97] active:bg-[#0f2e1c]"
          >
            Got it
          </button>
        </footer>
      </div>
    </div>
  );
}