"use client";

/**
 * MobileWaterApp — state machine + screen switcher for the mobile
 * Tuxedo Water Log experience.
 *
 * Flow:
 *   pin ─verify─▶ tabbed shell
 *                      │
 *                      ├──[Headgates]── headgates ─pick─▶ log ─submit─▶ success
 *                      │             ▲                               │
 *                      │             │                               │
 *                      │             └───────── (logout) ────────────┤
 *                      │
 *                      └──[Time]──── TimeTrackingFieldClient (clock-in / clock-out)
 *
 * Hydration:
 *   - On mount we peek at `document.cookie` for the `wl_session`
 *     cookie (set by the server action `verifyWaterPin`). If it's
 *     present we skip the PIN screen and load headgates directly.
 *   - This mirrors the existing field-client behavior — irrigators
 *     who already signed in once in their shift shouldn't have to
 *     re-enter their PIN every time they reopen the page.
 *
 * Cycle 4 — the existing PIN is unified with the time-tracking PIN:
 * after `verifyWaterPin` succeeds we also best-effort call
 * `verifyTimeTrackingPin` with the same digits. If the worker exists
 * in `time_tracking_workers`, the time-tracking session cookie is set
 * too, so they never re-enter their PIN on the Time tab.
 *
 * Server actions used:
 *   - `verifyWaterPin`        → signs the irrigator in, sets cookie
 *   - `getWaterHeadgates`     → loads the list of active gates
 *   - `submitWaterEntry`      → posts a new reading
 *   - `logoutWater`           → clears the cookie + session row
 *   - `verifyTimeTrackingPin` → unifies PIN with Time tab (Cycle 4)
 *   - `logoutTimeTracking`    → clears the time-tracking cookie on logout
 */
import {
  useCallback,
  useEffect,
  useLayoutEffect,
  useRef,
  useState,
  type ReactNode,
} from "react";
import {
  verifyWaterPin,
  submitWaterEntry,
  getWaterHeadgates,
  logoutWater,
} from "@/actions/water-log/field";
import {
  verifyTimeTrackingPin,
  logoutTimeTracking,
} from "@/actions/time-tracking/field";
import TimeTrackingFieldClient from "@/components/time-tracking/TimeTrackingFieldClient";
import type {
  FieldHeadgate,
  Screen,
  SubmittedLog,
  ActiveTab,
} from "./types";
import { MobileWaterPinScreen } from "./PinScreen";
import { MobileWaterHeadgateList } from "./HeadgateList";
import { MobileWaterLogForm } from "./LogForm";
import { MobileWaterSuccessScreen } from "./SuccessScreen";
import { WL_SESSION_COOKIE } from "@/lib/water-log/session";
import {
  TUXEDO_BRAND_ID,
  TUXEDO_BRAND_NAME,
  TUXEDO_BRAND_ACCENT,
  TUXEDO_BRAND_LOGO_URL,
} from "@/lib/water-log/brand";
import { useLang } from "@/lib/water-log/lang-context";

// Server-side auth errors that should bounce the user to the PIN
// screen rather than surface as a read error. Matched against the
// exact strings returned by `requireFieldSession` in
// src/actions/water-log/auth.ts.
const SESSION_ERRORS = [
  "Session expired",
  "Session not found",
  "User is inactive",
  "Not logged in",
];

export function MobileWaterApp() {
  const [screen, setScreen] = useState<Screen>("pin");
  const [activeTab, setActiveTab] = useState<ActiveTab>("headgates");
  // `useLang()` reads from the <LangProvider> in WaterFieldClient,
  // so it's always up-to-date when the user toggles via the toggle
  // in the headgate list header.
  const { t } = useLang();
  const [irrigatorName, setIrrigatorName] = useState<string | null>(null);
  const [headgates, setHeadgates] = useState<FieldHeadgate[]>([]);
  const [loadingHeadgates, setLoadingHeadgates] = useState(false);
  const [headgateError, setHeadgateError] = useState<string | null>(null);
  const [selectedHeadgate, setSelectedHeadgate] =
    useState<FieldHeadgate | null>(null);
  const [submittedLog, setSubmittedLog] = useState<SubmittedLog | null>(null);
  // Monotonic counter used to remount the log form on "Log Another"
  // (so the input + notes clear deterministically without an
  // impure Date.now() call in render).
  const [logFormKey, setLogFormKey] = useState(0);

  // ── Headgate fetch (shared by initial load + pull-to-refresh) ──
  const loadHeadgates = useCallback(async () => {
    setLoadingHeadgates(true);
    setHeadgateError(null);
    try {
      const hgs = await getWaterHeadgates(TUXEDO_BRAND_ID, true);
      setHeadgates(hgs.filter((h) => h.active));
    } catch (err) {
      setHeadgateError(
        err instanceof Error ? err.message : t.headgates.loadError,
      );
    } finally {
      setLoadingHeadgates(false);
    }
  }, [t.headgates.loadError]);

  // ── Initial bootstrap: if `wl_session` exists, jump straight to
  //    the headgate list. We defer the cookie read into an effect
  //    so SSR markup matches (avoids hydration warnings). ────────
  useEffect(() => {
    if (typeof document === "undefined") return;
    const hasSession = document.cookie
      .split(";")
      .some((c) => c.trim().startsWith(`${WL_SESSION_COOKIE}=`));
    if (!hasSession) return;
    // Wrap setScreen in a microtask so the call doesn't fire
    // synchronously inside the effect body.
    queueMicrotask(() => {
      setScreen("headgates");
      void loadHeadgates();
    });
  }, [loadHeadgates]);

  // ── PIN submit ────────────────────────────────────────────────
  const handlePinSubmit = useCallback(
    async (pin: string) => {
      const result = await verifyWaterPin(TUXEDO_BRAND_ID, pin);
      if (!result.success) {
        // Trigger the shake animation on the PIN screen.
        if (typeof window !== "undefined") {
          window.dispatchEvent(new Event("water-pin-shake"));
        }
        throw new Error(result.error);
      }
      // If the user turns out to be a water_admin, the legacy flow
      // bounces them to /water/admin. Preserve that for parity.
      if (result.role === "water_admin") {
        window.location.href = "/water/admin";
        return;
      }
      setIrrigatorName(result.name ?? null);
      setScreen("headgates");
      void loadHeadgates();
      // Cycle 4 — unify the PIN with Time Tracking. Best-effort: a
      // worker who isn't also set up as a time-tracking worker just
      // sees the regular PIN screen on the Time tab. Never blocks
      // the water flow, never surfaces a 500 to the user.
      void verifyTimeTrackingPin(TUXEDO_BRAND_ID, pin).catch(() => {});
    },
    [loadHeadgates],
  );

  // ── Headgate select ───────────────────────────────────────────
  const handleHeadgateSelect = useCallback((hg: FieldHeadgate) => {
    setSelectedHeadgate(hg);
    setScreen("log");
  }, []);

  // ── Submit entry ──────────────────────────────────────────────
  const handleLogSubmit = useCallback(
    async (input: { measurement: number; unit: string; notes: string }) => {
      if (!selectedHeadgate) {
        throw new Error(t.log.noHeadgateSelected);
      }
      const result = await submitWaterEntry(
        selectedHeadgate.id,
        input.measurement,
        input.unit,
        input.notes,
      );
      if (!result.success) {
        if (SESSION_ERRORS.includes(result.error)) {
          setScreen("pin");
          throw new Error(t.log.sessionExpired);
        }
        throw new Error(t.log.submitError);
      }
      setSubmittedLog({
        headgateName: selectedHeadgate.name,
        measurement: input.measurement,
        unit: input.unit,
        timestamp: new Date(),
      });
      setScreen("success");
    },
    [selectedHeadgate, t.log.noHeadgateSelected, t.log.sessionExpired, t.log.submitError],
  );

  // ── Success actions ───────────────────────────────────────────
  const handleLogAnother = useCallback(() => {
    // Stay on the same headgate but remount the form so the input
    // + notes clear. Bumping the state key is deterministic —
    // never call Date.now() (or any impure fn) during render.
    setLogFormKey((k) => k + 1);
    setSubmittedLog(null);
    setScreen("log");
  }, []);

  const handleBackToHeadgates = useCallback(() => {
    setSubmittedLog(null);
    setSelectedHeadgate(null);
    setScreen("headgates");
    void loadHeadgates();
  }, [loadHeadgates]);

  // ── Logout (from anywhere) ────────────────────────────────────
  const handleLogout = useCallback(async () => {
    // Cycle 4 — clear BOTH cookies. The water flow only knew about
    // `wl_session`; the Time tab introduced `time_tracking_session`.
    try {
      await logoutWater();
    } catch {
      // Even if the server logout fails, clear local state so the
      // user isn't stuck.
    }
    try {
      await logoutTimeTracking();
    } catch {
      // Same — defensive cleanup.
    }
    setIrrigatorName(null);
    setHeadgates([]);
    setSelectedHeadgate(null);
    setSubmittedLog(null);
    setActiveTab("headgates");
    setScreen("pin");
  }, []);

  // ── Tab change (Cycle 4) ──────────────────────────────────────
  const handleTabChange = useCallback((next: ActiveTab) => {
    setActiveTab(next);
    if (next === "time") {
      // Switching to Time drops the in-flight water flow (selected
      // headgate + submitted log) so it doesn't linger when the
      // worker comes back to Headgates. The headgate list itself
      // is cached and will reload on demand.
      setSubmittedLog(null);
      setSelectedHeadgate(null);
      setScreen("time");
    } else {
      // Switching back to Headgates — restore the flow at the
      // headgate list (we always have that state cached; the
      // loadHeadgates effect picks up fresh data on mount).
      if (headgates.length === 0) void loadHeadgates();
      setScreen("headgates");
    }
  }, [headgates.length, loadHeadgates]);

  // ── Switch on the current screen ──────────────────────────────
  // Compute the screen element first, then wrap it once in
  // <LangProvider>. Wrapping per-case would remount the provider on
  // every screen change, wiping lang state.
  let screenElement: React.ReactNode = null;
  switch (screen) {
    case "pin":
      screenElement = <MobileWaterPinScreen onSubmit={handlePinSubmit} />;
      break;

    case "headgates":
      screenElement = (
        <MobileWaterHeadgateList
          irrigatorName={irrigatorName ?? undefined}
          headgates={headgates}
          loading={loadingHeadgates}
          error={headgateError}
          onSelect={handleHeadgateSelect}
          onRefresh={loadHeadgates}
          onLogout={handleLogout}
        />
      );
      break;

    case "log":
      if (!selectedHeadgate) {
        // Defensive: shouldn't happen, but bounce back to list.
        queueMicrotask(() => setScreen("headgates"));
        screenElement = null;
        break;
      }
      screenElement = (
        <MobileWaterLogForm
          key={`${selectedHeadgate.id}-${logFormKey}`}
          headgate={selectedHeadgate}
          irrigatorName={irrigatorName ?? undefined}
          onSubmit={handleLogSubmit}
          onChangeHeadgate={handleBackToHeadgates}
          onLogout={handleLogout}
        />
      );
      break;

    case "success":
      if (!submittedLog) {
        queueMicrotask(() => setScreen("headgates"));
        screenElement = null;
        break;
      }
      screenElement = (
        <MobileWaterSuccessScreen
          headgateName={submittedLog.headgateName}
          measurement={submittedLog.measurement}
          unit={submittedLog.unit}
          timestamp={submittedLog.timestamp}
          irrigatorName={irrigatorName ?? undefined}
          onLogAnother={handleLogAnother}
          onBackToHeadgates={handleBackToHeadgates}
        />
      );
      break;

    case "time":
      // Cycle 4 — Tuxedo-only Time tab. Same brand id / name /
      // accent as the standalone /tuxedo/time-clock route so the
      // embedded surface matches the standalone one pixel-for-pixel.
      screenElement = (
        <TimeTrackingFieldClient
          brandId={TUXEDO_BRAND_ID}
          brandName={TUXEDO_BRAND_NAME}
          brandAccent={TUXEDO_BRAND_ACCENT}
          logoUrl={TUXEDO_BRAND_LOGO_URL}
        />
      );
      break;
  }

  // ── Post-PIN shell ────────────────────────────────────────────
  // The pre-PIN screen is bare (no nav). After PIN we wrap the
  // screen element in a sticky tab bar so the worker can flip
  // between Headgates and Time without re-entering credentials.
  const isPostPin = screen !== "pin";

  return (
    <div className="flex h-full flex-col">
      {isPostPin && (
        <TabBar
          activeTab={activeTab}
          onChange={handleTabChange}
          onLogout={handleLogout}
          irrigatorName={irrigatorName ?? undefined}
        />
      )}
      <div className="min-h-0 flex-1">{screenElement}</div>
    </div>
  );
}

// ── TabBar (Cycle 4 → Apple HIG pass) ──────────────────────────
// Sticky top nav for the Tuxedo worker experience. Stays visible
// across Headgates → Log → Success AND on the Time tab.
//
// Two HIG principles in play:
//   1. Use UISegmentedControl for the mode switch (not two pills).
//      The control is a true iOS segmented control: gray track,
//      sliding white pill, spring timing. The same widget appears
//      as the unit selector inside the log form, just bigger.
//   2. Use a "liquid glass" chrome on the bar itself: gradient
//      background, strong saturate(180%), inner top highlight, and
//      a hairline at the bottom edge — not a flat colored band.
function TabBar({
  activeTab,
  onChange,
  onLogout,
  irrigatorName,
}: {
  activeTab: ActiveTab;
  onChange: (next: ActiveTab) => void;
  onLogout: () => void;
  irrigatorName?: string;
}) {
  const t = useLang().t;
  return (
    <div
      className="sticky top-0 z-30 flex items-center justify-between px-3"
      style={{
        paddingTop: "env(safe-area-inset-top)",
        height: "calc(44px + env(safe-area-inset-top))",
        // "Liquid glass": top catches more light than the bottom,
        // so the gradient runs brighter at the top and slightly
        // more transparent below. Saturation boost makes any
        // background read with a hint of brand warmth as the
        // worker scrolls headgates past the bar.
        background:
          "linear-gradient(180deg, rgba(255,255,255,0.88) 0%, rgba(255,255,255,0.62) 100%)",
        backdropFilter: "blur(24px) saturate(180%)",
        WebkitBackdropFilter: "blur(24px) saturate(180%)",
        // Inner highlight + hairline (one box-shadow with two
        // insets reads cheaper than separate pseudo-elements).
        boxShadow:
          "inset 0 0.5px 0 0 rgba(255,255,255,1), inset 0 -0.5px 0 0 rgba(0,0,0,0.06)",
      }}
    >
      <MiniSegmented
        ariaLabel="Worker navigation"
        value={activeTab}
        onChange={onChange}
        options={[
          { value: "headgates", label: t.tab.headgates },
          { value: "time", label: t.tab.time },
        ]}
      />
      <div className="flex items-center gap-1.5 pr-0.5">
        {irrigatorName && (
          <span className="hidden text-[12px] font-medium text-[rgba(60,60,67,0.55)] sm:inline">
            {irrigatorName}
          </span>
        )}
        <button
          type="button"
          onClick={onLogout}
          aria-label={t.tab.logout}
          className="rounded-full px-2.5 py-1 text-[13px] font-semibold text-[#14532d] active:bg-[rgba(120,120,128,0.16)]"
        >
          {t.tab.logout}
        </button>
      </div>
    </div>
  );
}

// ── MiniSegmented (nav-bar-sized UISegmentedControl) ──────────────
// Sliding-pill variant of SegmentedControl tuned for the 32pt nav
// bar context. Reuses the same measurement + spring-timing approach
// as the larger control in `SegmentedControl.tsx` so the two feel
// like one widget at different sizes.
function MiniSegmented<V extends string>({
  options,
  value,
  onChange,
  ariaLabel,
}: {
  options: ReadonlyArray<{ value: V; label: ReactNode }>;
  value: V;
  onChange: (next: V) => void;
  ariaLabel: string;
}) {
  const containerRef = useRef<HTMLDivElement>(null);
  // Sliding indicator rect. The indicator uses
  // `transform: translateX(...)` with Apple's spring curve so the
  // transition reads as "physical," not "fade."
  const [rect, setRect] = useState<{ x: number; w: number } | null>(null);

  const measure = useCallback(() => {
    const root = containerRef.current;
    if (!root) return;
    const btn = root.querySelector<HTMLButtonElement>(
      `[data-seg-key="${value}"]`,
    );
    if (!btn) return;
    const rootRect = root.getBoundingClientRect();
    const btnRect = btn.getBoundingClientRect();
    // `x` is measured from the container's content edge (after the
    // 2px padding) so the indicator rides just inside the track.
    setRect({ x: btnRect.left - rootRect.left - 2, w: btnRect.width });
  }, [value]);

  useLayoutEffect(() => {
    measure();
  }, [measure]);

  // Re-measure whenever the option set changes (e.g. labels
  // translate via the Lang context and the active segment width
  // shifts) so the indicator stays in place.
  useLayoutEffect(() => {
    const root = containerRef.current;
    if (!root) return;
    const ro = new ResizeObserver(measure);
    ro.observe(root);
    return () => ro.disconnect();
  }, [measure]);

  return (
    <div
      ref={containerRef}
      role="tablist"
      aria-label={ariaLabel}
      className="relative inline-flex h-8 items-stretch rounded-[9px] p-[2px]"
      style={{ background: "rgba(120,120,128,0.20)" }}
    >
      {rect && (
        <div
          aria-hidden
          className="absolute rounded-[7px] bg-white"
          style={{
            top: 2,
            bottom: 2,
            left: 2,
            width: rect.w,
            transform: `translateX(${rect.x}px)`,
            boxShadow:
              "0 3px 8px rgba(0,0,0,0.06), 0 3px 1px rgba(0,0,0,0.04), inset 0 -0.5px 0 0 rgba(0,0,0,0.04), inset 0 0.5px 0 0 rgba(255,255,255,1)",
            transition:
              "transform 320ms cubic-bezier(0.32, 0.72, 0, 1), width 320ms cubic-bezier(0.32, 0.72, 0, 1)",
          }}
        />
      )}
      {options.map((opt) => {
        const selected = opt.value === value;
        return (
          <button
            key={opt.value}
            type="button"
            role="tab"
            aria-selected={selected}
            data-seg-key={opt.value}
            onClick={() => onChange(opt.value)}
            className={[
              "relative z-10 select-none px-3 text-[13px] font-semibold",
              "flex items-center justify-center",
              "min-w-[78px] active:opacity-80",
              selected ? "text-[#1d1d1f]" : "text-[rgba(60,60,67,0.65)]",
            ].join(" ")}
            style={{
              height: 28,
              transition:
                "color 220ms cubic-bezier(0.32, 0.72, 0, 1)",
            }}
          >
            <span className="truncate">{opt.label}</span>
          </button>
        );
      })}
    </div>
  );
}

export default MobileWaterApp;