/* SER-OS — shared primitives: buttons, inputs, status pills */

function Button({ variant = "primary-navy", size = "lg", icon, iconRight, children, onClick, disabled, style }) {
  const heights = { lg: 56, md: 44, sm: 32 };
  const padX = { lg: 24, md: 20, sm: 12 };
  const fontSize = { lg: 16, md: 16, sm: 12 };
  const variants = {
    "primary-navy": { bg: "#003169", fg: "#FFFFFF", border: "transparent" },
    "primary-slate": { bg: "#574F40", fg: "#FFFFFF", border: "transparent" },
    "primary-tan": { bg: "#C8B597", fg: "#FFFFFF", border: "transparent" },
    "secondary": { bg: "#F9F8F5", fg: "#1F3A60", border: "transparent" },
    "outline": { bg: "#FFFFFF", fg: "#1F3A60", border: "#E5E7EB" },
    "dashed": { bg: "#FFFFFF", fg: "#1F3A60", border: "#D1D5DB", dashed: true },
    "ghost": { bg: "transparent", fg: "#1F3A60", border: "transparent" }
  };
  const v = variants[variant];
  return (
    <button
      onClick={disabled ? undefined : onClick}
      disabled={disabled}
      style={{
        height: heights[size],
        padding: `0 ${padX[size]}px`,
        borderRadius: 8,
        background: v.bg,
        color: v.fg,
        border: v.border === "transparent" ? "none" : `1px ${v.dashed ? "dashed" : "solid"} ${v.border}`,
        fontFamily: "inherit",
        fontSize: fontSize[size],
        fontWeight: 500,
        letterSpacing: size === "lg" || size === "md" ? "-0.313px" : "0",
        display: "inline-flex",
        alignItems: "center",
        justifyContent: "center",
        gap: size === "sm" ? 4 : 8,
        cursor: disabled ? "not-allowed" : "pointer",
        opacity: disabled ? 0.3 : 1,
        transition: "background 150ms ease",
        ...style
      }}>
      
      {icon}
      {children}
      {iconRight}
    </button>);

}

function IconButton({ children, onClick, style }) {
  return (
    <button
      onClick={onClick}
      style={{
        width: 36, height: 36, borderRadius: 8, background: "transparent",
        color: "#1F3A60", border: "none", display: "inline-flex",
        alignItems: "center", justifyContent: "center", cursor: "pointer",
        ...style
      }}>
      
      {children}
    </button>);

}

function EditPill({ onClick, children = "Edit" }) {
  return (
    <button onClick={onClick} style={{
      height: 24, padding: "0 8px", borderRadius: 4,
      background: "#F9F8F5", color: "#1F3A60",
      fontFamily: "inherit", fontSize: 12, fontWeight: 500,
      display: "inline-flex", alignItems: "center", gap: 4,
      border: "none", cursor: "pointer"
    }}>
      <SerIcons.Edit size={12} />
      {children}
    </button>);

}

function Tag({ children, variant = "light", wrap = false }) {
  const variants = {
    light: { bg: "#E4E4E2", fg: "#1F3A60" },
    cream: { bg: "#CCD6E1", fg: "#1F3A60" }
  };
  const v = variants[variant];
  return (
    <span style={{
      ...(wrap
        ? { padding: "4px 8px", lineHeight: "16px", textAlign: "center" }
        : { height: 24, padding: "0 8px", lineHeight: "24px" }),
      borderRadius: 4,
      background: v.bg, color: v.fg,
      fontSize: 12, fontWeight: 500,
      display: "inline-flex", alignItems: "center",
      whiteSpace: wrap ? "normal" : "nowrap",
    }}>{children}</span>);
}

function StatusPill({ status, compact }) {
  // Canonical statuses: Draft · Awaiting Signature · Confirmed · Cancelled
  // Amendment Draft is a sub-state of Confirmed shown during mid-edit.
  const styles = {
    // ── Three primary case statuses ──────────────────────────────────
    "Draft":              { bg: "#EEF1F5", fg: "#4A607A" },       // working / no pill (but kept for compat)
    "Awaiting Signature": { bg: "#FDF4E3", fg: "#9A6B1F" },       // FD App sent → Odoo awaiting
    "Confirmed":          { bg: "#E8F0E4", fg: "#5B7D4F" },       // Odoo signed → synced back
    // ── Operational sub-state (FD only) ──────────────────────────────
    "Amendment Draft":    { bg: "#E8EDF5", fg: "#2D4E7D" },
    // ── Cancelled ────────────────────────────────────────────────────
    "Cancelled":          { bg: "#FCE7E5", fg: "#A35854" },
  };
  const s = styles[status] || { bg: "#EEF1F5", fg: "#4A607A" };
  return (
    <span style={{
      padding: compact ? "2px 10px" : "5px 14px",
      borderRadius: 9999,
      background: s.bg, color: s.fg,
      fontSize: compact ? 12 : 13, fontWeight: 500,
      lineHeight: compact ? "16px" : "18px",
      display: "inline-flex", alignItems: "center",
    }}>{status}</span>);

}

function Input({ icon, value, onChange, placeholder, type = "text", style, onFocus, onBlur }) {
  const [focused, setFocused] = React.useState(false);
  return (
    <div style={{ position: "relative", ...style }}>
      {icon &&
      <span style={{
        position: "absolute", left: 14, top: "50%", transform: "translateY(-50%)",
        color: "#9CA8B8", display: "flex", pointerEvents: "none"
      }}>{icon}</span>
      }
      <input
        type={type}
        value={value || ""}
        onChange={onChange}
        placeholder={placeholder}
        onFocus={(e) => { setFocused(true); onFocus && onFocus(e); }}
        onBlur={(e)  => { setFocused(false); onBlur && onBlur(e); }}
        style={{
          width: "100%", boxSizing: "border-box",
          height: 48, padding: `0 14px 0 ${icon ? 40 : 14}px`,
          fontFamily: "inherit", fontSize: 14, color: "#1F3A60",
          background: "#FFFFFF",
          border: `1px solid ${focused ? "#1F3A60" : "#E5E7EB"}`,
          boxShadow: focused ? "0 0 0 3px rgba(31,58,96,0.12)" : "none",
          borderRadius: 8, outline: "none",
          letterSpacing: "-0.15px",
          transition: "border-color 120ms, box-shadow 120ms"
        }} />
      
    </div>);

}

const PHONE_COUNTRY_CODES = ["+65", "+60", "+61", "+44", "+1", "+86", "+91"];

function PhoneInput({ value, onChange, placeholder, style }) {
  const parse = (v) => {
    if (!v) return { cc: "+65", local: "" };
    const s = String(v).trim();
    const found = PHONE_COUNTRY_CODES.find((c) => s.startsWith(c + " ") || s === c);
    if (found) return { cc: found, local: s.slice(found.length).trimStart().replace(/\s+/g, "") };
    if (s.startsWith("+")) {
      const sp = s.indexOf(" ");
      if (sp > 0) return { cc: s.slice(0, sp), local: s.slice(sp + 1).replace(/\s+/g, "") };
    }
    return { cc: "+65", local: s.replace(/\s+/g, "") };
  };

  const { cc, local } = parse(value);
  const [inputFocused, setInputFocused] = React.useState(false);
  const [touched, setTouched] = React.useState(false);

  const emit = (newCC, newLocal) => {
    const cleanLocal = newLocal.replace(/\s+/g, "");
    const combined = cleanLocal ? `${newCC} ${cleanLocal}` : newCC;
    onChange && onChange({ target: { value: combined } });
  };

  // Soft validation: only shown after blur, only for SG numbers with content
  const showHint = touched && !inputFocused && cc === "+65" && local.length > 0 && local.length !== 8;

  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 4, minWidth: 0, ...style }}>
      <div style={{ display: "flex", gap: 8, minWidth: 0 }}>
        <select
          value={cc}
          onChange={(e) => emit(e.target.value, local)}
          style={{
            width: 88, height: 48, padding: "0 8px 0 12px",
            fontFamily: "inherit", fontSize: 14, color: "#1F3A60",
            background: "#FFFFFF", border: "1px solid #E5E7EB",
            borderRadius: 8, outline: "none",
            appearance: "none", letterSpacing: "-0.15px", flexShrink: 0,
            backgroundImage: "url(\"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 20 20' fill='none'><path d='M5 8l5 5 5-5' stroke='%23718096' stroke-width='1.5' stroke-linecap='round'/></svg>\")",
            backgroundRepeat: "no-repeat", backgroundPosition: "right 8px center",
          }}
        >
          {PHONE_COUNTRY_CODES.map((c) => <option key={c}>{c}</option>)}
        </select>
        <input
          type="tel"
          value={local}
          onChange={(e) => emit(cc, e.target.value)}
          placeholder={placeholder || "Enter phone number"}
          onFocus={() => setInputFocused(true)}
          onBlur={() => { setInputFocused(false); setTouched(true); }}
          style={{
            flex: "1 1 auto", minWidth: 0, height: 48, padding: "0 14px",
            fontFamily: "inherit", fontSize: 14, color: "#1F3A60",
            background: "#FFFFFF",
            border: `1px solid ${inputFocused ? "#1F3A60" : "#E5E7EB"}`,
            boxShadow: inputFocused ? "0 0 0 3px rgba(31,58,96,0.12)" : "none",
            borderRadius: 8, outline: "none", letterSpacing: "-0.15px",
            transition: "border-color 120ms, box-shadow 120ms",
          }}
        />
      </div>
      {showHint && (
        <span style={{ fontSize: 12, color: "#9A6B1F", letterSpacing: "-0.1px" }}>
          Enter a valid 8-digit Singapore number
        </span>
      )}
    </div>
  );
}

function Textarea({ value, onChange, placeholder, rows = 4 }) {
  return (
    <textarea
      value={value || ""}
      onChange={onChange}
      placeholder={placeholder}
      rows={rows}
      style={{
        width: "100%", boxSizing: "border-box",
        padding: "10px 12px", fontFamily: "inherit",
        fontSize: 14, lineHeight: "20px", color: "#1F3A60",
        background: "#FFFFFF", border: "1px solid #E5E7EB",
        borderRadius: 8, outline: "none", resize: "vertical",
        letterSpacing: "-0.15px"
      }} />);


}

/* ============================================================
   Auto-save indicator (Google Drive–style)

   The indicator listens for two custom events on `window`:
     - "seros:dirty"      → user touched something; show "Saving…"
     - "seros:save-error" → save failed; show "Saving failed. Retrying…"

   Anywhere in the app, fire `window.markDirty()` and this widget
   will reflect the save state. State machine:

     idle ─[dirty]→ saving ─[850ms quiet]→ just-saved ─[5s]→ saved
                       │
                       ├─[more dirty events]→ restarts 850ms debounce
                       └─[error]→ error (sticky until next dirty)

   Because the indicator lives inside the sticky <Nav>, the state
   persists across page navigation without remounting.
   ============================================================ */
function SavedIndicator() {
  const [state, setState] = React.useState("saved");
  // saved | saving | just-saved | error
  const savingTimerRef = React.useRef(null);
  const justSavedTimerRef = React.useRef(null);

  React.useEffect(() => {
    const onDirty = () => {
      clearTimeout(savingTimerRef.current);
      clearTimeout(justSavedTimerRef.current);
      setState("saving");
      // After ~850ms of no further dirty events, treat the save as complete.
      savingTimerRef.current = setTimeout(() => {
        setState("just-saved");
        // "Saved just now" gently demotes to "Saved" after a few seconds.
        justSavedTimerRef.current = setTimeout(() => setState("saved"), 5000);
      }, 850);
    };
    const onError = () => {
      clearTimeout(savingTimerRef.current);
      clearTimeout(justSavedTimerRef.current);
      setState("error");
    };
    window.addEventListener("seros:dirty", onDirty);
    window.addEventListener("seros:save-error", onError);
    return () => {
      window.removeEventListener("seros:dirty", onDirty);
      window.removeEventListener("seros:save-error", onError);
      clearTimeout(savingTimerRef.current);
      clearTimeout(justSavedTimerRef.current);
    };
  }, []);

  const config = {
    "saved": { text: "Saved", dot: "#9CAE82", animating: false, fg: "#6B7A8F" },
    "saving": { text: "Saving\u2026", dot: "#9CA8B8", animating: true, fg: "#6B7A8F" },
    "just-saved": { text: "Saved just now", dot: "#9CAE82", animating: false, fg: "#574F40" },
    "error": { text: "Saving failed. Retrying\u2026", dot: "#A85C57", animating: true, fg: "#8F4A45" }
  }[state];

  return (
    <div
      aria-live="polite"
      role="status"
      style={{
        display: "inline-flex", alignItems: "center", gap: 8,
        color: config.fg,
        transition: "color 220ms ease",
        minWidth: 130
      }}>
      
      <span style={{
        position: "relative",
        width: 10, height: 10,
        display: "inline-flex", alignItems: "center", justifyContent: "center"
      }}>
        {/* Soft expanding ring while saving — barely-there reassurance */}
        {config.animating &&
        <span style={{
          position: "absolute", inset: 0,
          borderRadius: "50%",
          background: config.dot,
          animation: "seros-save-ring 1.6s ease-out infinite",
          opacity: 0.4
        }} />
        }
        <span style={{
          width: 8, height: 8, borderRadius: "50%",
          background: config.dot,
          animation: config.animating ? "seros-save-pulse 1.2s ease-in-out infinite" : "none",
          transition: "background 280ms ease"
        }} />
      </span>
      <span style={{
        fontSize: 14, fontWeight: 500, letterSpacing: "-0.15px",
        fontVariantNumeric: "tabular-nums",
        transition: "color 220ms ease"
      }}>
        {config.text}
      </span>
    </div>);

}

/* Fire from anywhere: `window.markDirty()` or `markDirty()` */
function markDirty() {window.dispatchEvent(new CustomEvent("seros:dirty"));}
function markSaveError() {window.dispatchEvent(new CustomEvent("seros:save-error"));}

/* ============================================================
   useSmartPosition — smart floating dropdown positioning

   Usage:
     const triggerRef = React.useRef(null);
     const dropStyle  = useSmartPosition(open, triggerRef, { maxHeight: 360 });

     <div ref={triggerRef}>...</div>
     {open && dropStyle && <div style={{ ...dropStyle, ...visualStyles }}>...</div>}

   Returns a `position: "fixed"` style object that:
   - Aligns the dropdown to the trigger's left or right edge
   - Opens downward unless there isn't enough space, then opens upward
   - Caps height to available viewport space (with min 60px safety floor)
   - Adds overflowY: "auto" so the list scrolls internally
   - Updates on every scroll and resize so the panel follows the trigger
   ============================================================ */
function useSmartPosition(open, triggerRef, { maxHeight = 340, minWidth = 0, align = "left" } = {}) {
  const [style, setStyle] = React.useState(null);
  React.useLayoutEffect(() => {
    if (!open || !triggerRef.current) { setStyle(null); return; }
    const compute = () => {
      const el = triggerRef.current;
      if (!el) return;
      const r = el.getBoundingClientRect();
      const vh = window.innerHeight;
      const vw = window.innerWidth;
      const gap = 6;
      const spaceBelow = vh - r.bottom - gap;
      const spaceAbove = r.top - gap;
      const preferUp = spaceBelow < 200 && spaceAbove > spaceBelow;
      const h = preferUp
        ? Math.min(maxHeight, Math.max(spaceAbove, 60))
        : Math.min(maxHeight, Math.max(spaceBelow, 60));
      setStyle({
        position: "fixed",
        ...(align === "right" ? { right: vw - r.right } : { left: r.left }),
        width: Math.max(r.width, minWidth),
        maxHeight: h,
        overflowY: "auto",
        zIndex: 9999,
        ...(preferUp ? { bottom: vh - r.top + gap } : { top: r.bottom + gap }),
      });
    };
    compute();
    window.addEventListener("scroll", compute, true);
    window.addEventListener("resize", compute);
    return () => {
      window.removeEventListener("scroll", compute, true);
      window.removeEventListener("resize", compute);
    };
  }, [open]);
  return style;
}

function fmtPhone(v) {
  if (!v) return v;
  const s = String(v).trim();
  if (s.startsWith("+")) {
    const sp = s.indexOf(" ");
    if (sp > 0) return `${s.slice(0, sp)} ${s.slice(sp + 1).replace(/\s+/g, "")}`;
  }
  return s.replace(/\s+/g, "");
}

Object.assign(window, { Button, IconButton, EditPill, Tag, StatusPill, Input, PhoneInput, Textarea, SavedIndicator, markDirty, markSaveError, useSmartPosition, fmtPhone });