const { useState, useEffect, useMemo, useCallback } = React;

const APP_VERSION = "7.0";   // must match APP_VERSION in server.js

/* ================================ api ================================ */

const tok = {
  get: () => localStorage.getItem("rits_token"),
  set: (t) => localStorage.setItem("rits_token", t),
  clear: () => localStorage.removeItem("rits_token"),
};

async function api(path, options = {}) {
  const res = await fetch("/api" + path, {
    method: options.method || "GET",
    headers: { "Content-Type": "application/json", Authorization: "Bearer " + (tok.get() || "") },
    body: options.body ? JSON.stringify(options.body) : undefined,
  });
  // A 401 on any normal call means the session died — sign out and reload.
  // A 401 from the login call itself is just a wrong password, and must fall
  // through so the caller can show the real message instead of crashing.
  if (res.status === 401 && path !== "/login") {
    tok.clear();
    location.reload();
    return;
  }
  let data = {};
  try { data = await res.json(); }
  catch {
    throw new Error(`The server replied with ${res.status} but not a readable message. ` +
      `Check the PowerShell window for the real error.`);
  }
  if (!res.ok) throw Object.assign(new Error(data.error || `Request failed (${res.status})`), { data });
  return data;
}

// Photos and documents are no longer public files — the session token rides
// along in the query string, because a browser can't put a header on an <img>.
const fileUrl = (u) => (!u ? "" : u + (u.includes("?") ? "&" : "?") + "t=" + (tok.get() || ""));

const fmt = (d) => d
  ? new Date(d + "T00:00:00").toLocaleDateString("en-GB", { day: "2-digit", month: "short", year: "numeric" })
  : "—";
const money = (n) => "SAR " + Number(n || 0).toLocaleString();

/* =============================== icons =============================== */

const I = ({ d, className = "w-5 h-5" }) => (
  <svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor"
       strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">{d}</svg>
);
const Icons = {
  dash: <I d={<><rect x="3" y="3" width="7" height="9"/><rect x="14" y="3" width="7" height="5"/><rect x="14" y="12" width="7" height="9"/><rect x="3" y="16" width="7" height="5"/></>} />,
  users: <I d={<><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/></>} />,
  doc: <I d={<><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="8" y1="13" x2="16" y2="13"/><line x1="8" y1="17" x2="13" y2="17"/></>} />,
  clip: <I d={<><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/><rect x="8" y="2" width="8" height="4" rx="1"/></>} />,
  gear: <I d={<><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.6 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.6 1.65 1.65 0 0 0 10 3.09V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9c.14.63.71 1.08 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></>} />,
  plus: <I d={<><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></>} />,
  pencil: <I d={<><path d="M17 3a2.8 2.8 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5z"/></>} className="w-4 h-4" />,
  trash: <I d={<><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/><path d="M10 11v6M14 11v6"/><path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/></>} className="w-4 h-4" />,
  search: <I d={<><circle cx="11" cy="11" r="7"/><line x1="21" y1="21" x2="16.7" y2="16.7"/></>} className="w-4 h-4" />,
  bell: <I d={<><path d="M18 8a6 6 0 1 0-12 0c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.7 21a2 2 0 0 1-3.4 0"/></>} className="w-4 h-4" />,
  chat: <I d={<><path d="M21 11.5a8.4 8.4 0 0 1-9 8.4 8.9 8.9 0 0 1-4-.9L3 21l1.9-4.9A8.4 8.4 0 0 1 12 3a8.4 8.4 0 0 1 9 8.5z"/></>} className="w-4 h-4" />,
  pin: <I d={<><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></>} className="w-4 h-4" />,
  cam: <I d={<><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></>} />,
  back: <I d={<polyline points="15 18 9 12 15 6"/>} />,
  x: <I d={<><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></>} />,
  star: <I d={<polygon points="12 2 15.1 8.6 22 9.3 17 14.1 18.2 21 12 17.7 5.8 21 7 14.1 2 9.3 8.9 8.6 12 2"/>} className="w-4 h-4" />,
  cal: <I d={<><rect x="3" y="4" width="18" height="18" rx="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></>} />,
  shield: <I d={<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>} className="w-4 h-4" />,
  down: <I d={<><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></>} className="w-4 h-4" />,
  up: <I d={<><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></>} className="w-4 h-4" />,
};

/* ============================= ui pieces ============================= */

const input = "w-full rounded-lg bg-white border border-gray-300 px-3 py-2.5 text-ink placeholder-ink-faint focus:border-brand-500 focus:ring-2 focus:ring-brand-100 focus:outline-none";
const card = "bg-white rounded-xl border border-gray-200";

function Field({ label, children, hint, required }) {
  return (
    <label className="block">
      <span className="text-sm font-medium text-ink-soft">
        {label}{required && <span className="text-rose-600"> *</span>}
      </span>
      {hint && <span className="block text-xs text-ink-faint">{hint}</span>}
      <div className="mt-1.5">{children}</div>
    </label>
  );
}

function Pills({ options, value, onChange, multi }) {
  const on = (o) => (multi ? (value || []).includes(o) : value === o);
  const click = (o) => multi
    ? onChange((value || []).includes(o) ? value.filter((v) => v !== o) : [...(value || []), o])
    : onChange(o);
  return (
    <div className="flex flex-wrap gap-2">
      {options.map((o) => (
        <button key={o} onClick={() => click(o)}
          className={"min-h-10 rounded-lg px-3 text-sm font-medium border " +
            (on(o) ? "bg-brand-500 text-white border-brand-500" : "bg-white text-ink-soft border-gray-300")}>
          {o}
        </button>
      ))}
    </div>
  );
}

const TONE = {
  "Active": "bg-emerald-50 text-emerald-700 border-emerald-200",
  "Expiring Soon": "bg-amber-50 text-amber-700 border-amber-200",
  "Expired": "bg-rose-50 text-rose-700 border-rose-200",
  "Ready for Install": "bg-emerald-50 text-emerald-700 border-emerald-200",
  "Need Quotation": "bg-sky-50 text-sky-700 border-sky-200",
  "Missing Materials": "bg-amber-50 text-amber-700 border-amber-200",
  "Blocked": "bg-rose-50 text-rose-700 border-rose-200",
};

const Badge = ({ status }) => (
  <span className={"rounded-md border px-2 py-0.5 text-xs font-medium whitespace-nowrap " + (TONE[status] || "bg-gray-100 text-ink-soft border-gray-200")}>
    {status}
  </span>
);

const Btn = ({ kind = "primary", className = "", ...p }) => {
  const styles = {
    primary: "bg-brand-500 text-white hover:bg-brand-600",
    soft: "bg-brand-50 text-brand-700 hover:bg-brand-100",
    ghost: "bg-white border border-gray-300 text-ink-soft hover:bg-gray-50",
    danger: "bg-rose-50 text-rose-700 border border-rose-200 hover:bg-rose-100",
  };
  return <button {...p} className={`min-h-10 rounded-lg px-3.5 text-sm font-medium inline-flex items-center justify-center gap-2 disabled:opacity-50 ${styles[kind]} ${className}`} />;
};

function IconBtn({ icon, title, onClick, tone = "text-ink-faint hover:text-ink" }) {
  return (
    <button onClick={onClick} title={title}
      className={"w-9 h-9 rounded-lg inline-flex items-center justify-center hover:bg-gray-100 " + tone}>
      {icon}
    </button>
  );
}

function Modal({ title, onClose, children, wide }) {
  return (
    <div className="fixed inset-0 z-50 bg-black/40 flex items-end sm:items-center justify-center p-0 sm:p-4"
         onClick={onClose}>
      <div onClick={(e) => e.stopPropagation()}
        className={"bg-white w-full rounded-t-2xl sm:rounded-2xl max-h-[92vh] overflow-y-auto " + (wide ? "sm:max-w-2xl" : "sm:max-w-lg")}>
        <div className="sticky top-0 bg-white border-b border-gray-200 px-4 py-3 flex items-center justify-between">
          <h3 className="font-semibold text-ink">{title}</h3>
          <IconBtn icon={Icons.x} onClick={onClose} title="Close" />
        </div>
        <div className="p-4">{children}</div>
      </div>
    </div>
  );
}

function Counter({ value, onChange, min = 0 }) {
  const v = Number(value) || 0;
  return (
    <div className="inline-flex items-center rounded-lg border border-gray-300 bg-white">
      <button onClick={() => onChange(Math.max(min, v - 1))}
        className="w-12 h-11 text-xl text-ink-soft">−</button>
      <input value={v} inputMode="numeric"
        onChange={(e) => onChange(Math.max(min, parseInt(e.target.value.replace(/[^0-9]/g, ""), 10) || 0))}
        className="w-14 h-11 text-center border-x border-gray-300 focus:outline-none" />
      <button onClick={() => onChange(v + 1)} className="w-12 h-11 text-xl text-ink-soft">+</button>
    </div>
  );
}

/**
 * WhatsApp sending.
 *
 * Opening web.whatsapp.com per message is painful: every click reloads the whole
 * of WhatsApp Web, and WhatsApp only allows one active web session, so a second
 * tab fights the first. For anyone sending to more than a handful of customers
 * that is unworkable.
 *
 * So messages now go through a small dialog which offers, in order of how well
 * they actually work:
 *   1. the installed WhatsApp app, via the whatsapp:// link - no browser tab at all
 *   2. WhatsApp Web, reusing one named tab rather than opening new ones
 *   3. copy the text, for pasting into a chat that is already open
 * The choice is remembered per device.
 */
let openWhatsAppDialog = null;   // set by the shell once it has mounted

const waDigits = (number) => String(number || "").replace(/[^0-9]/g, "");

function openWhatsApp(number, message, queue) {
  const n = waDigits(number);
  if (!queue && n.length < 8) {
    alert("No usable WhatsApp number saved for this contact.\n\n" +
      "Add one with the country code, for example +966501234567.");
    return;
  }
  if (openWhatsAppDialog) return openWhatsAppDialog({ number: n, message, queue });
  window.open(`https://wa.me/${n}?text=${encodeURIComponent(message)}`, "ritsWhatsApp");
}

const waPref = {
  get: () => localStorage.getItem("rits_wa_method") || "app",
  set: (v) => localStorage.setItem("rits_wa_method", v),
};

function WhatsAppDialog({ job, onClose }) {
  const queue = job.queue || null;
  const [i, setI] = useState(0);
  const item = queue ? queue[i] : job;
  const [text, setText] = useState(item.message);
  const [sent, setSent] = useState([]);
  const [method, setMethod] = useState(waPref.get());

  useEffect(() => { setText(item.message); }, [i, item.message]);

  const number = waDigits(item.number);
  const valid = number.length >= 8;

  function send(via) {
    if (!valid) return alert("This contact has no usable WhatsApp number.");
    waPref.set(via); setMethod(via);
    const enc = encodeURIComponent(text);
    if (via === "app") {
      // Hands off to the installed WhatsApp; the browser stays where it is.
      window.location.href = `whatsapp://send?phone=${number}&text=${enc}`;
    } else {
      const w = window.open(`https://web.whatsapp.com/send?phone=${number}&text=${enc}`, "ritsWhatsApp");
      if (w) w.focus();
      else alert("Your browser blocked the window. Allow pop-ups for this site.");
    }
    if (queue) markSent();
  }

  function copy() {
    navigator.clipboard?.writeText(text)
      .then(() => alert("Message copied. Paste it into the chat."))
      .catch(() => alert("Could not copy. Select the text and copy it by hand."));
  }

  function markSent() {
    if (!queue) return;
    setSent((s) => [...new Set([...s, i])]);
    if (i < queue.length - 1) setTimeout(() => setI(i + 1), 400);
  }

  return (
    <Modal title={queue ? `Send reminders (${i + 1} of ${queue.length})` : "Send on WhatsApp"}
      onClose={onClose} wide>
      <div className="space-y-3">
        {queue && (
          <div className="flex flex-wrap gap-1.5">
            {queue.map((q, n) => (
              <button key={n} onClick={() => setI(n)}
                className={"w-8 h-8 rounded-lg text-xs font-medium border " +
                  (sent.includes(n) ? "bg-brand-500 text-white border-brand-500"
                    : n === i ? "bg-white text-brand-700 border-brand-500"
                    : "bg-white text-ink-faint border-gray-300")}>
                {n + 1}
              </button>
            ))}
          </div>
        )}

        <div className={card + " p-3"}>
          <p className="text-sm font-medium text-ink">{item.name || "Customer"}</p>
          <p className={"text-sm font-mono " + (valid ? "text-ink-soft" : "text-rose-600")}>
            {item.number || "no number"} {valid ? "" : "— not a usable number"}
          </p>
        </div>

        <Field label="Message" hint="Edit it if you want before sending">
          <textarea rows="7" className={input} value={text} onChange={(e) => setText(e.target.value)} />
        </Field>

        <div className="flex flex-wrap gap-2">
          <Btn onClick={() => send("app")} disabled={!valid}
            className={method === "app" ? "" : "opacity-90"}>
            {Icons.chat} Open WhatsApp app
          </Btn>
          <Btn kind="ghost" onClick={() => send("web")} disabled={!valid}>WhatsApp Web</Btn>
          <Btn kind="ghost" onClick={copy}>Copy text</Btn>
        </div>

        <p className="text-xs text-ink-faint">
          The app button uses the WhatsApp program installed on this computer or phone, so no
          browser tab is opened. If nothing happens, WhatsApp Desktop is not installed — use
          WhatsApp Web instead, or install it from whatsapp.com/download.
        </p>

        {queue && (
          <div className="flex flex-wrap gap-2 pt-2 border-t border-gray-100">
            <Btn kind="ghost" onClick={() => setI(Math.max(0, i - 1))} disabled={i === 0}>Previous</Btn>
            <Btn kind="soft" onClick={markSent}>Mark done and next</Btn>
            <Btn kind="ghost" onClick={() => setI(Math.min(queue.length - 1, i + 1))}
              disabled={i >= queue.length - 1}>Skip</Btn>
            <span className="ml-auto text-sm text-ink-faint self-center">
              {sent.length} of {queue.length} done
            </span>
          </div>
        )}
      </div>
    </Modal>
  );
}

/**
 * Signature pad. Works with finger, stylus or mouse. Draws at the canvas's real
 * pixel size so the line lands under the fingertip on a high-density screen.
 */
function SignaturePad({ onSave, onCancel, initialName }) {
  const ref = React.useRef(null);
  const [name, setName] = useState(initialName || "");
  const [drawn, setDrawn] = useState(false);
  const drawing = React.useRef(false);

  useEffect(() => {
    const cv = ref.current;
    if (!cv) return;
    const ratio = window.devicePixelRatio || 1;
    cv.width = cv.offsetWidth * ratio;
    cv.height = cv.offsetHeight * ratio;
    const ctx = cv.getContext("2d");
    ctx.scale(ratio, ratio);
    ctx.lineWidth = 2.2;
    ctx.lineCap = "round";
    ctx.lineJoin = "round";
    ctx.strokeStyle = "#1a1a1a";
  }, []);

  const pos = (e) => {
    const r = ref.current.getBoundingClientRect();
    const p = e.touches ? e.touches[0] : e;
    return { x: p.clientX - r.left, y: p.clientY - r.top };
  };

  function start(e) {
    e.preventDefault();
    drawing.current = true;
    const ctx = ref.current.getContext("2d");
    const { x, y } = pos(e);
    ctx.beginPath();
    ctx.moveTo(x, y);
  }
  function move(e) {
    if (!drawing.current) return;
    e.preventDefault();
    const ctx = ref.current.getContext("2d");
    const { x, y } = pos(e);
    ctx.lineTo(x, y);
    ctx.stroke();
    setDrawn(true);
  }
  const end = () => { drawing.current = false; };

  function clear() {
    const cv = ref.current;
    cv.getContext("2d").clearRect(0, 0, cv.width, cv.height);
    setDrawn(false);
  }

  function save() {
    if (!drawn) return alert("Ask the customer to sign in the box first.");
    if (!name.trim()) return alert("Enter the name of the person signing.");
    // Flatten onto white so the PNG is not a transparent smudge in the PDF.
    const cv = ref.current;
    const flat = document.createElement("canvas");
    flat.width = cv.width; flat.height = cv.height;
    const fx = flat.getContext("2d");
    fx.fillStyle = "#ffffff";
    fx.fillRect(0, 0, flat.width, flat.height);
    fx.drawImage(cv, 0, 0);
    onSave(flat.toDataURL("image/png"), name.trim());
  }

  return (
    <div className="space-y-3">
      <p className="text-sm text-ink-soft">
        Hand the device to the customer. Signing confirms the work recorded here.
      </p>
      <canvas ref={ref} className="w-full h-44 bg-white border-2 border-dashed border-gray-300 rounded-xl touch-none"
        onMouseDown={start} onMouseMove={move} onMouseUp={end} onMouseLeave={end}
        onTouchStart={start} onTouchMove={move} onTouchEnd={end} />
      <Field label="Name of person signing" required>
        <input className={input} value={name} onChange={(e) => setName(e.target.value)}
          placeholder="Mr. Ahmed, Facility Manager" />
      </Field>
      <div className="flex gap-2">
        <Btn kind="ghost" onClick={onCancel}>Cancel</Btn>
        <Btn kind="ghost" onClick={clear}>Clear</Btn>
        <Btn onClick={save} className="flex-1">Save signature</Btn>
      </div>
    </div>
  );
}

/**
 * Downloads a report as a real file, then offers it to the device's share sheet
 * so WhatsApp receives an actual PDF attachment rather than a link.
 * File sharing needs a secure (https) origin; otherwise the file is saved and
 * the person attaches it themselves.
 */
async function sharePdfFile({ url, filename, title, text }) {
  let file;
  try {
    const res = await fetch(url, { headers: { Authorization: "Bearer " + (tok.get() || "") } });
    if (!res.ok) throw new Error(`The server returned ${res.status}`);
    const blob = await res.blob();
    file = new File([blob], filename, { type: "application/pdf" });
  } catch (e) {
    alert("Could not build the PDF: " + e.message);
    return;
  }

  if (navigator.canShare && navigator.canShare({ files: [file] })) {
    try {
      await navigator.share({ files: [file], title, text });
      return;
    } catch (e) {
      if (e && e.name === "AbortError") return;   // user closed the sheet
    }
  }

  const a = document.createElement("a");
  a.href = URL.createObjectURL(file);
  a.download = filename;
  a.click();
  setTimeout(() => URL.revokeObjectURL(a.href), 10000);
  alert(`${filename} has been saved to your downloads.\n\n` +
    (window.isSecureContext
      ? "Direct sharing is not available in this browser - attach the file in WhatsApp."
      : "Direct sharing needs a secure https address. On a phone over https you get the share sheet instead."));
}

function Empty({ text, sub }) {
  return (
    <div className="rounded-xl border border-dashed border-gray-300 p-10 text-center">
      <p className="font-medium text-ink-soft">{text}</p>
      {sub && <p className="text-sm text-ink-faint mt-1">{sub}</p>}
    </div>
  );
}

/* ============================== login ============================== */

function Login({ onIn }) {
  const [u, setU] = useState(""); const [p, setP] = useState("");
  const [err, setErr] = useState(""); const [busy, setBusy] = useState(false);

  async function submit() {
    setBusy(true); setErr("");
    try {
      const r = await api("/login", { method: "POST", body: { username: u, password: p } });
      tok.set(r.token); onIn();
    } catch (e) { setErr(e.message); setBusy(false); }
  }

  return (
    <div className="min-h-screen flex items-center justify-center px-5 bg-white">
      <div className="w-full max-w-sm">
        <img src="/img/sitecare-primary-light.svg" alt="SiteCare — site survey & AMC management"
          className="w-full max-w-xs mx-auto mb-10" />
        <div className="space-y-4">
          <Field label="Username">
            <input className={input} value={u} autoCapitalize="none" onChange={(e) => setU(e.target.value)} />
          </Field>
          <Field label="Password">
            <input className={input} type="password" value={p}
              onChange={(e) => setP(e.target.value)} onKeyDown={(e) => e.key === "Enter" && submit()} />
          </Field>
          {err && <p className="text-sm text-rose-600">{err}</p>}
          <Btn onClick={submit} disabled={busy} className="w-full">{busy ? "Signing in…" : "Sign in"}</Btn>
        </div>
      </div>
    </div>
  );
}

/* ============================ dashboard ============================ */

function Dashboard({ me, go }) {
  const [d, setD] = useState(null);
  const [view, setView] = useState(null);   // null | Active | Expiring Soon | Expired | followups
  const [q, setQ] = useState("");
  const [forecast, setForecast] = useState([]);

  const load = useCallback(() => api("/dashboard").then(setD).catch(() => setD(null)), []);
  useEffect(() => { load(); api("/forecast").then(setForecast).catch(() => {}); }, [load]);

  if (!d) return <p className="text-ink-faint">Loading…</p>;
  const s = d.summary;

  const cards = [
    { key: "Active", n: s.active, label: "Active AMCs", tone: "text-emerald-600" },
    { key: "Expiring Soon", n: s.expiring, label: "Expiring in 30 days", tone: "text-amber-600" },
    { key: "Expired", n: s.expired, label: "Expired", tone: "text-rose-600" },
    { key: "followups", n: s.followups, label: "Pending follow-ups", tone: "text-brand-600" },
  ];

  const rows = view === "followups"
    ? d.surveys.filter((x) => x.status !== "Ready for Install")
    : view ? d.contracts.filter((c) => c.status === view) : [];

  const filtered = rows.filter((r) =>
    (r.client_name || "").toLowerCase().includes(q.toLowerCase()));

  return (
    <div className="space-y-5">
      <div className="flex items-center justify-between">
        <div>
          <h2 className="text-xl font-semibold text-ink">Dashboard</h2>
          <p className="text-sm text-ink-faint">{s.clients} clients · {money(s.value_at_risk)} up for renewal</p>
        </div>
      </div>

      <div className="grid grid-cols-2 md:grid-cols-4 gap-3">
        {cards.map((c) => (
          <button key={c.key} onClick={() => setView(view === c.key ? null : c.key)}
            className={card + " p-4 text-left transition hover:border-brand-300 " +
              (view === c.key ? "border-brand-500 ring-2 ring-brand-100" : "")}>
            <p className={"text-3xl font-semibold " + c.tone}>{c.n}</p>
            <p className="text-xs text-ink-faint mt-1">{c.label}</p>
            <p className="text-xs text-brand-600 mt-2">{view === c.key ? "Hide list" : "Tap to list"}</p>
          </button>
        ))}
      </div>

      {view && (
        <div className="space-y-3">
          <div className="flex items-center gap-2">
            <h3 className="font-semibold text-ink">
              {view === "followups" ? "Surveys waiting on something" : view + " contracts"}
            </h3>
            <span className="text-sm text-ink-faint">({filtered.length})</span>
            <button onClick={() => setView(null)} className="ml-auto text-sm text-brand-600">Clear</button>
          </div>

          <div className="relative">
            <span className="absolute left-3 top-3 text-ink-faint">{Icons.search}</span>
            <input className={input + " pl-9"} placeholder="Search this list"
              value={q} onChange={(e) => setQ(e.target.value)} />
          </div>

          {filtered.length === 0 ? <Empty text="Nothing in this list" /> : filtered.map((r) =>
            view === "followups" ? (
              <button key={r.id} onClick={() => go("surveys")} className={card + " p-4 w-full text-left"}>
                <div className="flex justify-between gap-3">
                  <div className="min-w-0">
                    <p className="font-medium text-ink truncate">{r.client_name}</p>
                    <p className="text-sm text-ink-faint">{r.job_type} · {(r.created_at || "").slice(0, 10)}</p>
                  </div>
                  <Badge status={r.status} />
                </div>
                {r.handoff_notes && <p className="text-sm text-ink-soft mt-2 line-clamp-2">{r.handoff_notes}</p>}
              </button>
            ) : (
              <button key={r.id} onClick={() => go("contracts")} className={card + " p-4 w-full text-left"}>
                <div className="flex justify-between gap-3">
                  <div className="min-w-0">
                    <p className="font-medium text-ink truncate">{r.client_name}</p>
                    <p className="text-sm text-ink-faint truncate">{r.contract_no || "no number"} · {r.frequency}</p>
                  </div>
                  <Badge status={r.status} />
                </div>
                <div className="flex flex-wrap gap-x-4 gap-y-1 mt-2 text-xs text-ink-faint">
                  <span>Ends {fmt(r.end_date)}</span>
                  <span>{r.days_left < 0 ? `${Math.abs(r.days_left)} days overdue` : `${r.days_left} days left`}</span>
                  <span>{money(r.value)}</span>
                </div>
              </button>
            )
          )}
        </div>
      )}

      {!view && (s.visits_overdue > 0 || s.visits_due > 0) && (
        <button onClick={() => go("visits")} className={card + " p-4 w-full text-left"}>
          <h3 className="font-semibold text-ink">Maintenance visits</h3>
          <p className="text-sm text-ink-faint mt-1">
            {s.visits_overdue > 0 && <span className="text-rose-600">{s.visits_overdue} overdue</span>}
            {s.visits_overdue > 0 && s.visits_due > 0 && " · "}
            {s.visits_due > 0 && <span>{s.visits_due} due in the next 14 days</span>}
          </p>
          <p className="text-xs text-brand-600 mt-2">Open the visit list</p>
        </button>
      )}

      {!view && forecast.length > 0 && (
        <div className={card + " p-4"}>
          <h3 className="font-semibold text-ink mb-1">Renewal value by month</h3>
          <p className="text-sm text-ink-faint mb-3">What comes up for renewal over the year ahead.</p>
          {forecast.map((r) => {
            const max = Math.max(...forecast.map((x) => x.value)) || 1;
            return (
              <div key={r.month} className="flex items-center gap-3 py-1">
                <span className="text-xs text-ink-faint w-16 shrink-0">{r.month}</span>
                <div className="flex-1 h-2 rounded-full bg-gray-100 overflow-hidden">
                  <div className="h-full rounded-full bg-brand-400"
                       style={{ width: `${Math.max(3, (r.value / max) * 100)}%` }} />
                </div>
                <span className="text-xs text-ink-faint w-24 text-right shrink-0">{money(r.value)}</span>
              </div>
            );
          })}
        </div>
      )}

      {!view && (
        <div className={card + " p-4"}>
          <h3 className="font-semibold text-ink mb-1">Next 5 renewals</h3>
          <p className="text-sm text-ink-faint mb-3">Tap any number above to see the full list.</p>
          {d.contracts.slice(0, 5).map((c) => (
            <div key={c.id} className="flex items-center justify-between py-2 border-t border-gray-100 first:border-0">
              <div className="min-w-0">
                <p className="text-sm font-medium text-ink truncate">{c.client_name}</p>
                <p className="text-xs text-ink-faint">Ends {fmt(c.end_date)}</p>
              </div>
              <Badge status={c.status} />
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

/* ============================== clients ============================== */

const BLANK_CLIENT = { name: "", contact_person: "", phone: "", email: "", whatsapp: "", site_address: "", city: "Al Jubail", vat_number: "", cr_number: "", notes: "" };

function Clients({ me }) {
  const [rows, setRows] = useState(null);
  const [q, setQ] = useState("");
  const [edit, setEdit] = useState(null);
  const can = (p) => me.permissions.includes(p);

  const load = useCallback(() => api("/clients?q=" + encodeURIComponent(q)).then(setRows), [q]);
  useEffect(() => { load(); }, [load]);

  async function save(c) {
    try {
      if (c.id) await api("/clients/" + c.id, { method: "PUT", body: c });
      else await api("/clients", { method: "POST", body: c });
      setEdit(null); load();
    } catch (e) {
      // The server refuses a likely duplicate; the user decides.
      if (e.data?.needs_confirm && e.data?.duplicates) {
        const names = e.data.duplicates.map((d) => `• ${d.name} (${d.reasons.join(", ")})`).join("\n");
        if (confirm(`This may already exist:\n\n${names}\n\nAdd it anyway as a separate client?`)) {
          try {
            await api("/clients", { method: "POST", body: { ...c, force: true } });
            setEdit(null); load();
          } catch (e2) { alert(e2.message); }
        }
        return;
      }
      alert(e.message);
    }
  }

  async function remove(c) {
    if (!confirm(`Delete client "${c.name}"?`)) return;
    try { await api("/clients/" + c.id, { method: "DELETE" }); load(); }
    catch (e) {
      if (e.data?.needs_confirm && confirm(e.message + "\n\nDelete anyway?")) {
        await api("/clients/" + c.id + "?force=1", { method: "DELETE" }); load();
      } else alert(e.message);
    }
  }

  return (
    <div className="space-y-4">
      <div className="flex items-center justify-between">
        <h2 className="text-xl font-semibold text-ink">Clients</h2>
        {can("clients.edit") &&
          <Btn onClick={() => setEdit({ ...BLANK_CLIENT })}>{Icons.plus} Client</Btn>}
      </div>

      <div className="relative">
        <span className="absolute left-3 top-3 text-ink-faint">{Icons.search}</span>
        <input className={input + " pl-9"} placeholder="Search name, address or phone"
          value={q} onChange={(e) => setQ(e.target.value)} />
      </div>

      {rows === null ? <p className="text-ink-faint">Loading…</p>
        : rows.length === 0 ? <Empty text="No clients yet" sub="Add your first client to start tracking contracts." />
        : <div className="grid gap-3 md:grid-cols-2 2xl:grid-cols-3">{rows.map((c) => (
          <div key={c.id} className={card + " p-4"}>
            <div className="flex justify-between gap-2">
              <div className="min-w-0">
                {c.client_code && <p className="text-xs font-mono text-brand-600">{c.client_code}</p>}
                <p className="font-medium text-ink">{c.name}</p>
                <p className="text-sm text-ink-faint">{[c.contact_person, c.phone].filter(Boolean).join(" · ")}</p>
                {c.site_address && <p className="text-sm text-ink-faint mt-0.5">{c.site_address}</p>}
                <div className="mt-2 space-y-0.5">
                  {c.email
                    ? <p className="text-xs text-ink-soft break-all">✉ {c.email}</p>
                    : <p className="text-xs text-rose-600">No email — renewal reminders can't reach this client</p>}
                  {c.whatsapp
                    ? <p className="text-xs text-ink-soft">✆ {c.whatsapp}</p>
                    : <p className="text-xs text-amber-600">No WhatsApp number saved</p>}
                </div>
                <div className="flex gap-3 mt-2 text-xs text-ink-faint">
                  <span>{c.contract_count} contract{c.contract_count === 1 ? "" : "s"}</span>
                  <span>{c.survey_count} survey{c.survey_count === 1 ? "" : "s"}</span>
                </div>
              </div>
              <div className="flex gap-1 shrink-0">
                {can("clients.edit") && <IconBtn icon={Icons.pencil} title="Edit" onClick={() => setEdit(c)} />}
                {can("clients.delete") && <IconBtn icon={Icons.trash} title="Delete"
                  tone="text-rose-400 hover:text-rose-600" onClick={() => remove(c)} />}
              </div>
            </div>
          </div>
        ))}</div>}

      {edit && (
        <Modal title={edit.id ? "Edit client" : "New client"} onClose={() => setEdit(null)}>
          <div className="space-y-3">
            <Field label="Client name" required><input className={input} value={edit.name || ""}
              onChange={(e) => setEdit({ ...edit, name: e.target.value })} /></Field>
            <div className="grid sm:grid-cols-2 gap-3">
              <Field label="Contact person"><input className={input} value={edit.contact_person || ""}
                onChange={(e) => setEdit({ ...edit, contact_person: e.target.value })} /></Field>
              <Field label="Phone"><input className={input} value={edit.phone || ""}
                onChange={(e) => setEdit({ ...edit, phone: e.target.value })} /></Field>
            </div>
            <Field label="Email" hint="Renewal reminders are sent here" required>
              <input className={input} value={edit.email || ""}
                onChange={(e) => setEdit({ ...edit, email: e.target.value })} /></Field>
            <Field label="WhatsApp number" hint="With country code, e.g. +966501234567" required>
              <input className={input} value={edit.whatsapp || ""}
                onChange={(e) => setEdit({ ...edit, whatsapp: e.target.value })} /></Field>
            <div className="grid sm:grid-cols-2 gap-3">
              <Field label="VAT number" hint="15 digits">
                <input className={input} value={edit.vat_number || ""} placeholder="3xxxxxxxxxxxxx3"
                  onChange={(e) => setEdit({ ...edit, vat_number: e.target.value })} /></Field>
              <Field label="CR number">
                <input className={input} value={edit.cr_number || ""} placeholder="1010xxxxxx"
                  onChange={(e) => setEdit({ ...edit, cr_number: e.target.value })} /></Field>
            </div>
            <Field label="Site address"><textarea rows="2" className={input} value={edit.site_address || ""}
              onChange={(e) => setEdit({ ...edit, site_address: e.target.value })} /></Field>
            <Field label="Notes"><textarea rows="2" className={input} value={edit.notes || ""}
              onChange={(e) => setEdit({ ...edit, notes: e.target.value })} /></Field>
            <div className="flex gap-2 pt-2">
              <Btn kind="ghost" onClick={() => setEdit(null)}>Cancel</Btn>
              <Btn onClick={() => save(edit)} className="flex-1">Save client</Btn>
            </div>
          </div>
        </Modal>
      )}
    </div>
  );
}

/* =========================== amc contracts =========================== */

const SERVICE_TYPES = ["CCTV", "Networking", "Software", "Other"];

const BLANK_CONTRACT = { client_id: "", contract_no: "", service_type: "CCTV", software_name: "",
  scope: "", start_date: "", end_date: "", value: "", frequency: "Quarterly", notes: "",
  renewal_raised: 0, po_number: "" };

function Contracts({ me }) {
  const [rows, setRows] = useState(null);
  const [clients, setClients] = useState([]);
  const [q, setQ] = useState("");
  const [filter, setFilter] = useState("All");
  const [typeFilter, setTypeFilter] = useState("All types");
  const [edit, setEdit] = useState(null);
  const [busy, setBusy] = useState(false);
  const [status, setStatus] = useState(null);
  const can = (p) => me.permissions.includes(p);

  const load = useCallback(() => api("/contracts?q=" + encodeURIComponent(q)).then(setRows), [q]);
  useEffect(() => { load(); }, [load]);
  useEffect(() => { api("/clients").then(setClients).catch(() => {}); }, []);
  useEffect(() => { api("/notify-status").then(setStatus).catch(() => {}); }, []);

  const list = (rows || []).filter((r) =>
    (filter === "All" || r.status === filter) &&
    (typeFilter === "All types" || (r.service_type || "CCTV") === typeFilter));

  async function save(c) {
    try {
      if (c.id) await api("/contracts/" + c.id, { method: "PUT", body: c });
      else await api("/contracts", { method: "POST", body: c });
      setEdit(null); load();
    } catch (e) { alert(e.message); }
  }

  async function remove(c) {
    if (!confirm(`Delete contract ${c.contract_no || ""} for ${c.client_name}?`)) return;
    await api("/contracts/" + c.id, { method: "DELETE" }); load();
  }

  async function notifyNow(c) {
    if (!confirm(`Send a renewal reminder to ${c.client_name} now?`)) return;
    setBusy(true);
    try {
      const r = await api(`/contracts/${c.id}/notify`, { method: "POST" });
      alert(r.results.map(explain).join("\n\n"));
    } catch (e) { alert(e.message); } finally { setBusy(false); }
  }

  // Turns a delivery status into something a person can act on.
  function explain(x) {
    const ch = x.channel === "email" ? "Email" : "WhatsApp";
    if (x.status === "sent") return `${ch}: sent to ${x.detail}`;
    if (x.status === "no_address") return `${ch}: not sent. ${x.detail}. Add it under Clients, then try again.`;
    if (x.status === "failed") return `${ch}: failed. ${x.detail}`;
    if (x.status === "not_configured") {
      return x.channel === "email"
        ? "Email: not sent. Email sending isn't set up yet — add your SMTP details under Settings → Notifications. The message was written to the server window instead."
        : "WhatsApp: not sent automatically. Use the green WhatsApp button on this contract — it works without any setup.";
    }
    return `${ch}: ${x.status}`;
  }

  async function renew(c) {
    if (!confirm("Create a new one-year contract starting tomorrow?")) return;
    await api(`/contracts/${c.id}/renew`, { method: "POST", body: {} }); load();
  }

  // Sends the customer a rating link. 4-5 stars offers the Google review page;
  // 1-3 stars routes the complaint privately to the office instead.
  async function askFeedback(c) {
    if (!confirm(`Send a feedback request to ${c.client_name} on WhatsApp?`)) return;
    try {
      const r = await api("/feedback/request", {
        method: "POST",
        body: { client_id: c.client_id, contract_id: c.id, channel: "whatsapp" },
      });
      openWhatsApp(c.whatsapp || c.phone, r.message);
    } catch (e) { alert(e.message); }
  }

  const dueSoon = (rows || []).filter((r) => r.days_left <= 30 && !r.renewal_raised);

  const renewalText = (c) =>
    `Dear ${c.client_name},\n\nYour AMC ${c.contract_no || ""} expires on ${c.end_date}` +
    ` (${c.days_left < 0 ? "already expired" : c.days_left + " days left"}).\n\n` +
    `Shall we prepare the renewal quotation?\n\nReliable iT - Al Jubail`;

  function whatsappRenewal(c) {
    openWhatsApp(c.whatsapp || c.phone, renewalText(c));
  }

  /**
   * One dialog, one message at a time, with a numbered strip showing progress.
   * Nothing opens until you press send, so a hundred customers do not turn into
   * a hundred browser tabs.
   */
  function bulkRemind() {
    const queue = dueSoon.map((c) => ({
      name: c.client_name,
      number: c.whatsapp || c.phone,
      message: renewalText(c),
    }));
    if (!queue.length) return alert("Nothing is expiring in the next 30 days.");
    openWhatsApp(null, null, queue);
  }

  return (
    <div className="space-y-4">
      {status && !status.email_configured && (
        <div className="rounded-xl border border-amber-200 bg-amber-50 p-3">
          <p className="text-sm text-amber-800">
            Automatic email reminders are scheduled but can't send yet — no mail account is
            configured. Add your SMTP details under Settings → Notifications to switch them on.
          </p>
        </div>
      )}
      <div className="flex items-start justify-between gap-2">
        <div>
          <h2 className="text-xl font-semibold text-ink">AMC contracts</h2>
          {dueSoon.length > 0 && can("reminders.send") && (
            <button onClick={bulkRemind} className="text-sm text-brand-600 text-left">
              Send WhatsApp reminders to all {dueSoon.length} expiring contract{dueSoon.length === 1 ? "" : "s"}
            </button>
          )}
        </div>
        {can("contracts.edit") &&
          <Btn onClick={() => setEdit({ ...BLANK_CONTRACT, client_id: clients[0]?.id || "" })}>
            {Icons.plus} Contract
          </Btn>}
      </div>

      <div className="relative">
        <span className="absolute left-3 top-3 text-ink-faint">{Icons.search}</span>
        <input className={input + " pl-9"} placeholder="Search client, number or scope"
          value={q} onChange={(e) => setQ(e.target.value)} />
      </div>

      <Pills options={["All", "Active", "Expiring Soon", "Expired"]} value={filter} onChange={setFilter} />
      <Pills options={["All types", ...SERVICE_TYPES]} value={typeFilter} onChange={setTypeFilter} />

      {rows === null ? <p className="text-ink-faint">Loading…</p>
        : list.length === 0 ? <Empty text="No contracts here" sub="Add an AMC contract to start the reminder clock." />
        : <div className="grid gap-3 md:grid-cols-2 2xl:grid-cols-3">{list.map((c) => (
          <div key={c.id} className={card + " p-4"}>
            <div className="flex justify-between gap-2">
              <div className="min-w-0">
                <p className="font-medium text-ink truncate">{c.client_name}</p>
                <p className="text-sm text-ink-faint truncate">
                  {c.contract_no || "no number"} · {c.service_type || "CCTV"}
                  {c.service_type === "Software" && c.software_name ? ` (${c.software_name})` : ""} · {c.frequency}
                </p>
              </div>
              <div className="flex items-start gap-1 shrink-0">
                <Badge status={c.status} />
              </div>
            </div>

            <div className="grid grid-cols-2 gap-y-1 mt-3 text-xs text-ink-faint">
              <span>Start {fmt(c.start_date)}</span>
              <span>End {fmt(c.end_date)}</span>
              <span>{c.days_left < 0 ? `${Math.abs(c.days_left)} days overdue` : `${c.days_left} days left`}</span>
              <span>{money(c.value)}</span>
              {c.next_visit && <span className="col-span-2">Next visit {fmt(c.next_visit)}</span>}
            </div>

            {!c.email && (
              <p className="text-xs text-rose-600 mt-2">
                This client has no email address — reminders can't be sent until one is added.
              </p>
            )}

            <div className="flex flex-wrap items-center gap-2 mt-3 pt-3 border-t border-gray-100">
              {can("reminders.send") && (
                <>
                  <Btn kind="soft" onClick={() => notifyNow(c)} disabled={busy}>{Icons.bell} Remind</Btn>
                  <Btn kind="soft" onClick={() => whatsappRenewal(c)}
                    className="bg-emerald-50 text-emerald-700 hover:bg-emerald-100">
                    {Icons.chat} WhatsApp
                  </Btn>
                </>
              )}
              {can("contracts.edit") && c.days_left <= 30 &&
                <Btn kind="ghost" onClick={() => renew(c)}>Renew 1 year</Btn>}
              {can("contracts.edit") &&
                <Btn kind="ghost" onClick={() => askFeedback(c)}>{Icons.star} Feedback</Btn>}
              <div className="ml-auto flex gap-1">
                {can("contracts.edit") && <IconBtn icon={Icons.pencil} title="Edit" onClick={() => setEdit(c)} />}
                {can("contracts.delete") && <IconBtn icon={Icons.trash} title="Delete"
                  tone="text-rose-400 hover:text-rose-600" onClick={() => remove(c)} />}
              </div>
            </div>
          </div>
        ))}</div>}

      {edit && (
        <Modal title={edit.id ? "Edit contract" : "New AMC contract"} onClose={() => setEdit(null)}>
          <div className="space-y-3">
            <Field label="Client">
              <select className={input} value={edit.client_id}
                onChange={(e) => setEdit({ ...edit, client_id: Number(e.target.value) })}>
                <option value="">Select a client</option>
                {clients.map((c) => <option key={c.id} value={c.id}>{c.name}</option>)}
              </select>
            </Field>
            <div className="grid sm:grid-cols-2 gap-3">
              <Field label="Contract number" hint="Leave blank for an automatic one">
                <input className={input} value={edit.contract_no || ""} placeholder="auto"
                  onChange={(e) => setEdit({ ...edit, contract_no: e.target.value })} /></Field>
              <Field label="Value (SAR)"><input type="number" className={input} value={edit.value || ""}
                onChange={(e) => setEdit({ ...edit, value: e.target.value })} /></Field>
            </div>
            <Field label="Type of AMC">
              <Pills options={SERVICE_TYPES} value={edit.service_type || "CCTV"}
                onChange={(v) => setEdit({ ...edit, service_type: v })} />
            </Field>
            {edit.service_type === "Software" && (
              <Field label="Software name" hint="The accounting or billing system this AMC covers">
                <input className={input} value={edit.software_name || ""} placeholder="Tally Prime, Zoho Books…"
                  onChange={(e) => setEdit({ ...edit, software_name: e.target.value })} /></Field>
            )}
            <Field label="Scope"><input className={input} value={edit.scope || ""} placeholder="CCTV + network AMC"
              onChange={(e) => setEdit({ ...edit, scope: e.target.value })} /></Field>
            <div className="grid sm:grid-cols-2 gap-3">
              <Field label="Start date"><input type="date" className={input} value={edit.start_date || ""}
                onChange={(e) => setEdit({ ...edit, start_date: e.target.value })} /></Field>
              <Field label="End date"><input type="date" className={input} value={edit.end_date || ""}
                onChange={(e) => setEdit({ ...edit, end_date: e.target.value })} /></Field>
            </div>
            <Field label="Service frequency" hint="Maintenance visits are scheduled automatically from this">
              <Pills options={["Monthly", "Quarterly", "Bi-Annually", "Annually"]} value={edit.frequency}
                onChange={(v) => setEdit({ ...edit, frequency: v })} />
            </Field>
            <label className="flex items-center gap-3 rounded-lg border border-gray-200 p-3">
              <input type="checkbox" className="w-5 h-5 accent-brand-500" checked={!!edit.renewal_raised}
                onChange={(e) => setEdit({ ...edit, renewal_raised: e.target.checked ? 1 : 0 })} />
              <span className="text-sm text-ink-soft">Renewal quotation already sent — stop auto reminders</span>
            </label>
            <Field label="Customer PO number" hint="From your ERP, for reference here">
              <input className={input} value={edit.po_number || ""}
                onChange={(e) => setEdit({ ...edit, po_number: e.target.value })} /></Field>
            <Field label="Notes"><textarea rows="2" className={input} value={edit.notes || ""}
              onChange={(e) => setEdit({ ...edit, notes: e.target.value })} /></Field>
            {edit.id && <ContractDocs contractId={edit.id} />}
            <div className="flex gap-2 pt-2">
              <Btn kind="ghost" onClick={() => setEdit(null)}>Cancel</Btn>
              <Btn onClick={() => save(edit)} className="flex-1">Save contract</Btn>
            </div>
          </div>
        </Modal>
      )}
    </div>
  );
}

function ContractDocs({ contractId }) {
  const [docs, setDocs] = useState([]);
  const [busy, setBusy] = useState(false);
  const load = useCallback(() => api(`/contracts/${contractId}/docs`).then(setDocs).catch(() => {}), [contractId]);
  useEffect(() => { load(); }, [load]);

  async function upload(e) {
    const file = e.target.files?.[0];
    if (!file) return;
    if (file.size > 8 * 1024 * 1024) { alert("That file is larger than 8 MB."); e.target.value = ""; return; }
    setBusy(true);
    try {
      const data = await new Promise((res, rej) => {
        const fr = new FileReader();
        fr.onload = () => res(fr.result);
        fr.onerror = () => rej(new Error("Could not read that file"));
        fr.readAsDataURL(file);
      });
      await api(`/contracts/${contractId}/docs`, {
        method: "POST",
        body: { filename: file.name, data, doc_type: e.target.dataset.kind || "Other" },
      });
      load();
    } catch (err) { alert(err.message); } finally { setBusy(false); e.target.value = ""; }
  }

  async function remove(d) {
    if (!confirm(`Delete "${d.original}"?`)) return;
    await api(`/contracts/${contractId}/docs/${d.id}`, { method: "DELETE" });
    load();
  }

  return (
    <div className="rounded-xl border border-gray-200 p-3 space-y-2">
      <p className="text-sm font-medium text-ink-soft">Attached documents</p>
      <p className="text-xs text-ink-faint">The customer PO, the signed agreement, anything worth keeping with this contract.</p>
      {docs.map((d) => (
        <div key={d.id} className="flex items-center justify-between gap-2 border-t border-gray-100 pt-2">
          <a href={fileUrl("/documents/" + d.filename)} target="_blank" rel="noreferrer" className="min-w-0">
            <p className="text-sm text-brand-700 truncate">{d.original}</p>
            <p className="text-xs text-ink-faint">{d.doc_type} · {Math.round((d.size || 0) / 1024)} KB · {(d.created_at || "").slice(0, 10)}</p>
          </a>
          <IconBtn icon={Icons.trash} title="Delete" tone="text-rose-400 hover:text-rose-600" onClick={() => remove(d)} />
        </div>
      ))}
      <label className="inline-flex mt-1">
        <span className="min-h-9 rounded-lg px-3 text-sm font-medium inline-flex items-center gap-1.5 bg-brand-50 text-brand-700 cursor-pointer">
          {Icons.up} {busy ? "Uploading…" : "Attach PO or document"}
        </span>
        <input type="file" data-kind="PO" className="hidden" onChange={upload} />
      </label>
    </div>
  );
}

/* ============================== surveys ============================== */

/**
 * The CCTV questionnaire. Every branch hangs off a Yes/No above it, so a site
 * with no cameras answers one question and moves on. Values stay in the object
 * when a branch is hidden — nothing is lost if a technician flips an answer back
 * — but hidden branches are never shown on screen or printed in the report.
 */
const BLANK_CCTV = {
  available: "No",
  camera_type: "IP Camera",

  // IP path
  nvr: "No", nvr_brand: "", nvr_model: "", nvr_channels: "", nvr_connected: "", nvr_free: "",
  poe: "No", poe_brand: "", poe_model: "", poe_ports: "", poe_used: "", poe_free: "",
  network: "No", network_dedicated: "No", network_vlan: "No",

  // Analog path
  dvr: "No", dvr_brand: "", dvr_model: "", dvr_channels: "", dvr_connected: "", dvr_free: "",
  coax: "No", coax_type: "", coax_condition: "Good", coax_length: "",

  // Remote viewing — asked on both paths
  remote: "No", remote_mobile: "No", remote_pc: "No", remote_users: "",

  // Camera counts and specification
  cams_existing: 0, cams_working: 0, cams_not_working: 0, cams_new: 0,
  resolution: "4MP", resolution_other: "",
  cam_types: [],
  night_vision: "No", night_vision_ok: "No",

  ai: "No", ai_features: [],

  // Free number, not a preset list — customers ask for 7, 45, 90, 180 and
  // anything between, so a dropdown would only get in the way.
  recording: "No", recording_days: "",

  hdd: "No", hdd_capacity: "", hdd_count: "", hdd_health: "Good",
  storage_more: "No", storage_capacity: "", storage_hdd: "No", storage_nas: "No", storage_other: "",

  compliance_required: "No", cert_available: "No",
  cert_number: "", cert_issue: "", cert_expiry: "", cert_status: "Valid",
  compliant: "Yes", compliance_issues: "", compliance_remarks: "", compliance_photos: [],

  cabling: "No", cable_type: "Cat6", cable_condition: "Good", cable_replace: "No",

  ups: "No", ups_capacity: "", ups_working: "Yes", ups_backup: "",
  power_supply: "No", power_condition: "Good",

  cameras: [],   // one entry per existing camera

  amc: "No", amc_type: "Comprehensive AMC", amc_period: "1 Year", amc_period_other: "",
  amc_frequency: "Quarterly", amc_response: "24 Hours",
};

/**
 * Shrinks a camera photo before upload: a 4 MB phone JPEG becomes roughly
 * 150 KB, which decides whether an upload finishes on site 4G.
 */
function shrinkImage(file, max = 1000, quality = 0.7) {
  return new Promise((resolve, reject) => {
    const fr = new FileReader();
    fr.onerror = () => reject(new Error("Could not read that image"));
    fr.onload = () => {
      const img = new Image();
      img.onerror = () => reject(new Error("That file is not a readable image"));
      img.onload = () => {
        const scale = Math.min(1, max / Math.max(img.width, img.height));
        const cv = document.createElement("canvas");
        cv.width = Math.round(img.width * scale);
        cv.height = Math.round(img.height * scale);
        cv.getContext("2d").drawImage(img, 0, 0, cv.width, cv.height);
        resolve(cv.toDataURL("image/jpeg", quality));
      };
      img.src = fr.result;
    };
    fr.readAsDataURL(file);
  });
}

/** A small add/remove photo row, used where a full photo grid would be overkill. */
function PhotoStrip({ label, photos = [], onChange }) {
  const [busy, setBusy] = useState(false);
  async function add(e) {
    const files = Array.from(e.target.files || []);
    setBusy(true);
    try {
      const added = [];
      for (const file of files) {
        const data = await shrinkImage(file);
        const { url } = await api("/upload", { method: "POST", body: { filename: file.name, data } });
        added.push({ url, label: file.name });
      }
      onChange([...(photos || []), ...added]);
    } catch (err) { alert(err.message); } finally { setBusy(false); e.target.value = ""; }
  }
  return (
    <Field label={label}>
      <div className="flex flex-wrap gap-2 items-center">
        {(photos || []).map((p, i) => (
          <div key={i} className="relative">
            <img src={fileUrl(p.url)} alt="" className="h-20 w-28 object-cover rounded-lg border border-gray-200" />
            <button onClick={() => onChange(photos.filter((_, j) => j !== i))}
              className="absolute top-1 right-1 bg-white/90 rounded p-1 text-rose-600">{Icons.trash}</button>
          </div>
        ))}
        <label className="inline-flex">
          <span className="min-h-10 rounded-lg px-3 text-sm font-medium inline-flex items-center gap-2 bg-brand-50 text-brand-700 cursor-pointer">
            {Icons.cam} {busy ? "Uploading…" : "Add photo"}
          </span>
          <input type="file" accept="image/*" capture="environment" multiple className="hidden" onChange={add} />
        </label>
      </div>
    </Field>
  );
}

/* --------------------- CCTV conditional questionnaire --------------------- */

const YN = ({ label, value, onChange, hint }) => (
  <Field label={label} hint={hint}>
    <Pills options={["Yes", "No"]} value={value} onChange={onChange} />
  </Field>
);

const Txt = ({ label, value, onChange, placeholder, type = "text", hint }) => (
  <Field label={label} hint={hint}>
    <input type={type} className={input} value={value ?? ""} placeholder={placeholder}
      inputMode={type === "number" ? "numeric" : undefined}
      onChange={(e) => onChange(e.target.value)} />
  </Field>
);

const Box = ({ title, children }) => (
  <div className="rounded-xl border border-gray-200 bg-white p-4 space-y-3">
    {title && <h4 className="text-sm font-semibold text-ink">{title}</h4>}
    {children}
  </div>
);

/** One existing camera on site. */
function CameraRow({ cam, index, onChange, onRemove }) {
  const set = (k, v) => onChange({ ...cam, [k]: v });
  const [busy, setBusy] = useState(false);

  async function addPhoto(e) {
    const file = e.target.files?.[0];
    if (!file) return;
    setBusy(true);
    try {
      const data = await shrinkImage(file);
      const { url } = await api("/upload", { method: "POST", body: { filename: file.name, data } });
      set("photo", url);
    } catch (err) { alert(err.message); } finally { setBusy(false); e.target.value = ""; }
  }

  return (
    <div className="rounded-xl border border-gray-200 p-3 space-y-3">
      <div className="flex items-center justify-between">
        <p className="text-sm font-semibold text-ink">Camera {cam.no || index + 1}</p>
        <IconBtn icon={Icons.trash} title="Remove" tone="text-rose-400 hover:text-rose-600" onClick={onRemove} />
      </div>
      <div className="grid sm:grid-cols-2 gap-3">
        <Txt label="Camera number" value={cam.no} onChange={(v) => set("no", v)} placeholder="1" />
        <Txt label="Location" value={cam.location} onChange={(v) => set("location", v)} placeholder="Main gate" />
      </div>
      <Field label="Camera type">
        <Pills options={["Dome", "Bullet", "Turret", "PTZ", "Fisheye", "ANPR", "Other"]}
          value={cam.type} onChange={(v) => set("type", v)} />
      </Field>
      <div className="grid sm:grid-cols-2 gap-3">
        <Txt label="Brand" value={cam.brand} onChange={(v) => set("brand", v)} />
        <Txt label="Model" value={cam.model} onChange={(v) => set("model", v)} />
      </div>
      <Field label="Resolution">
        <Pills options={["2MP", "4MP", "5MP", "8MP / 4K", "Other"]} value={cam.resolution}
          onChange={(v) => set("resolution", v)} />
      </Field>
      <div className="grid sm:grid-cols-2 gap-3">
        <YN label="Working?" value={cam.working} onChange={(v) => set("working", v)} />
        <Field label="Image quality">
          <Pills options={["Good", "Average", "Poor"]} value={cam.image_quality}
            onChange={(v) => set("image_quality", v)} />
        </Field>
      </div>
      <div className="grid sm:grid-cols-2 gap-3">
        <Field label="Night vision">
          <Pills options={["Working", "Not Working"]} value={cam.night_vision}
            onChange={(v) => set("night_vision", v)} />
        </Field>
        <Field label="Cable condition">
          <Pills options={["Good", "Damaged"]} value={cam.cable} onChange={(v) => set("cable", v)} />
        </Field>
      </div>
      <Field label="Power">
        <Pills options={["OK", "Not OK"]} value={cam.power} onChange={(v) => set("power", v)} />
      </Field>
      <Field label="Remarks">
        <textarea rows="2" className={input} value={cam.remarks || ""}
          onChange={(e) => set("remarks", e.target.value)}
          placeholder="Lens dirty, housing cracked, cable exposed…" />
      </Field>
      {cam.photo ? (
        <div className="flex items-center gap-3">
          <img src={fileUrl(cam.photo)} alt="" className="h-20 w-28 object-cover rounded-lg border border-gray-200" />
          <Btn kind="ghost" onClick={() => set("photo", "")}>Remove photo</Btn>
        </div>
      ) : (
        <label className="inline-flex">
          <span className="min-h-10 rounded-lg px-3 text-sm font-medium inline-flex items-center gap-2 bg-brand-50 text-brand-700 cursor-pointer">
            {Icons.cam} {busy ? "Uploading…" : "Add photo"}
          </span>
          <input type="file" accept="image/*" capture="environment" className="hidden" onChange={addPhoto} />
        </label>
      )}
    </div>
  );
}

function CctvQuestionnaire({ c, set }) {
  const ip = c.camera_type === "IP Camera";

  const setCam = (i, cam) => set("cameras", c.cameras.map((x, j) => (j === i ? cam : x)));
  const addCam = () => set("cameras", [...c.cameras, { ...BLANK_CAMERA, no: String(c.cameras.length + 1) }]);
  const delCam = (i) => set("cameras", c.cameras.filter((_, j) => j !== i));

  return (
    <section className="space-y-4 border-t border-gray-200 pt-5">
      <h3 className="text-sm font-semibold text-brand-600">CCTV system</h3>

      <YN label="Is a CCTV camera system available on site?" value={c.available}
        onChange={(v) => set("available", v)} />

      {c.available === "No" ? (
        <p className="text-sm text-ink-faint">
          No CCTV on site — the rest of this section is skipped. Record what the customer
          wants under Customer requirements below.
        </p>
      ) : (
        <>
          <Field label="What type of camera system?">
            <Pills options={["IP Camera", "Analog Camera"]} value={c.camera_type}
              onChange={(v) => set("camera_type", v)} />
          </Field>

          {ip ? (
            <>
              <Box title="Recorder">
                <YN label="Is an NVR available?" value={c.nvr} onChange={(v) => set("nvr", v)} />
                {c.nvr === "Yes" && (
                  <>
                    <div className="grid sm:grid-cols-2 gap-3">
                      <Txt label="NVR brand" value={c.nvr_brand} onChange={(v) => set("nvr_brand", v)} />
                      <Txt label="NVR model" value={c.nvr_model} onChange={(v) => set("nvr_model", v)} />
                    </div>
                    <div className="grid sm:grid-cols-3 gap-3">
                      <Txt label="Channel capacity" type="number" value={c.nvr_channels} onChange={(v) => set("nvr_channels", v)} />
                      <Txt label="Cameras connected" type="number" value={c.nvr_connected} onChange={(v) => set("nvr_connected", v)} />
                      <Txt label="Free channels" type="number" value={c.nvr_free} onChange={(v) => set("nvr_free", v)} />
                    </div>
                  </>
                )}
              </Box>

              <Box title="PoE switch">
                <YN label="Is a PoE switch available?" value={c.poe} onChange={(v) => set("poe", v)} />
                {c.poe === "Yes" && (
                  <>
                    <div className="grid sm:grid-cols-2 gap-3">
                      <Txt label="Switch brand" value={c.poe_brand} onChange={(v) => set("poe_brand", v)} />
                      <Txt label="Switch model" value={c.poe_model} onChange={(v) => set("poe_model", v)} />
                    </div>
                    <div className="grid sm:grid-cols-3 gap-3">
                      <Txt label="Port count" type="number" value={c.poe_ports} onChange={(v) => set("poe_ports", v)} />
                      <Txt label="Used ports" type="number" value={c.poe_used} onChange={(v) => set("poe_used", v)} />
                      <Txt label="Free ports" type="number" value={c.poe_free} onChange={(v) => set("poe_free", v)} />
                    </div>
                  </>
                )}
              </Box>

              <Box title="Network">
                <YN label="Is a CCTV network available?" value={c.network} onChange={(v) => set("network", v)} />
                {c.network === "Yes" && (
                  <div className="grid sm:grid-cols-2 gap-3">
                    <YN label="Dedicated CCTV network?" value={c.network_dedicated} onChange={(v) => set("network_dedicated", v)} />
                    <YN label="CCTV VLAN available?" value={c.network_vlan} onChange={(v) => set("network_vlan", v)} />
                  </div>
                )}
              </Box>
            </>
          ) : (
            <>
              <Box title="Recorder">
                <YN label="Is a DVR available?" value={c.dvr} onChange={(v) => set("dvr", v)} />
                {c.dvr === "Yes" && (
                  <>
                    <div className="grid sm:grid-cols-2 gap-3">
                      <Txt label="DVR brand" value={c.dvr_brand} onChange={(v) => set("dvr_brand", v)} />
                      <Txt label="DVR model" value={c.dvr_model} onChange={(v) => set("dvr_model", v)} />
                    </div>
                    <div className="grid sm:grid-cols-3 gap-3">
                      <Txt label="Channel capacity" type="number" value={c.dvr_channels} onChange={(v) => set("dvr_channels", v)} />
                      <Txt label="Cameras connected" type="number" value={c.dvr_connected} onChange={(v) => set("dvr_connected", v)} />
                      <Txt label="Free channels" type="number" value={c.dvr_free} onChange={(v) => set("dvr_free", v)} />
                    </div>
                  </>
                )}
              </Box>

              <Box title="Coaxial cabling">
                <YN label="Is coaxial cable available?" value={c.coax} onChange={(v) => set("coax", v)} />
                {c.coax === "Yes" && (
                  <>
                    <Txt label="Cable type" value={c.coax_type} onChange={(v) => set("coax_type", v)} placeholder="RG59 + 2C" />
                    <Field label="Cable condition">
                      <Pills options={["Good", "Average", "Poor", "Damaged"]} value={c.coax_condition}
                        onChange={(v) => set("coax_condition", v)} />
                    </Field>
                    <Txt label="Approximate length (metres)" type="number" value={c.coax_length}
                      onChange={(v) => set("coax_length", v)} />
                  </>
                )}
              </Box>
            </>
          )}

          <Box title="Remote viewing">
            <YN label="Is remote viewing available?" value={c.remote} onChange={(v) => set("remote", v)} />
            {c.remote === "Yes" && (
              <>
                <div className="grid sm:grid-cols-2 gap-3">
                  <YN label="Mobile viewing" value={c.remote_mobile} onChange={(v) => set("remote_mobile", v)} />
                  <YN label="PC / web viewing" value={c.remote_pc} onChange={(v) => set("remote_pc", v)} />
                </div>
                {ip && <Txt label="Number of users" type="number" value={c.remote_users}
                  onChange={(v) => set("remote_users", v)} />}
              </>
            )}
          </Box>

          <Box title="Camera count and specification">
            <div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
              <Field label="Existing"><Counter value={c.cams_existing} onChange={(v) => set("cams_existing", v)} /></Field>
              <Field label="Working"><Counter value={c.cams_working} onChange={(v) => set("cams_working", v)} /></Field>
              <Field label="Not working"><Counter value={c.cams_not_working} onChange={(v) => set("cams_not_working", v)} /></Field>
              <Field label="New required"><Counter value={c.cams_new} onChange={(v) => set("cams_new", v)} /></Field>
            </div>
            {Number(c.cams_working) + Number(c.cams_not_working) !== Number(c.cams_existing) && (
              <p className="text-xs text-amber-700">
                Working plus not-working is {Number(c.cams_working) + Number(c.cams_not_working)},
                but existing is {c.cams_existing}. Worth a second look.
              </p>
            )}
            <Field label="Camera resolution">
              <Pills options={["2MP", "4MP", "5MP", "8MP / 4K", "Other"]} value={c.resolution}
                onChange={(v) => set("resolution", v)} />
            </Field>
            {c.resolution === "Other" && <Txt label="Resolution (specify)" value={c.resolution_other}
              onChange={(v) => set("resolution_other", v)} />}
            <Field label="Camera types on site" hint="Choose as many as apply">
              <Pills multi options={["Dome", "Bullet", "Turret", "PTZ", "Fisheye", "ANPR", "Other"]}
                value={c.cam_types} onChange={(v) => set("cam_types", v)} />
            </Field>
            <YN label="Night vision available?" value={c.night_vision} onChange={(v) => set("night_vision", v)} />
            {c.night_vision === "Yes" &&
              <YN label="Night vision working properly?" value={c.night_vision_ok}
                onChange={(v) => set("night_vision_ok", v)} />}
          </Box>

          <Box title="AI features">
            <YN label="Are AI features required?" value={c.ai} onChange={(v) => set("ai", v)} />
            {c.ai === "Yes" && (
              <Field label="Which features?">
                <Pills multi value={c.ai_features} onChange={(v) => set("ai_features", v)}
                  options={["Human Detection", "Face Detection", "ANPR", "Line Crossing",
                            "Intrusion Detection", "People Counting", "Other"]} />
              </Field>
            )}
          </Box>

          <Box title="Recording">
            <YN label="Is recording required?" value={c.recording} onChange={(v) => set("recording", v)} />
            {c.recording === "Yes" &&
              <Txt label="Days of recording required" type="number" value={c.recording_days}
                onChange={(v) => set("recording_days", v)}
                hint="Type the number the customer asked for" placeholder="30" />}
          </Box>

          <Box title="Storage">
            <YN label="Is an HDD available?" value={c.hdd} onChange={(v) => set("hdd", v)} />
            {c.hdd === "Yes" && (
              <>
                <div className="grid sm:grid-cols-2 gap-3">
                  <Txt label="HDD capacity" value={c.hdd_capacity} onChange={(v) => set("hdd_capacity", v)} placeholder="4 TB" />
                  <Txt label="Number of HDDs" type="number" value={c.hdd_count} onChange={(v) => set("hdd_count", v)} />
                </div>
                <Field label="HDD health">
                  <Pills options={["Good", "Warning", "Failed"]} value={c.hdd_health}
                    onChange={(v) => set("hdd_health", v)} />
                </Field>
              </>
            )}
            <YN label="Additional storage required?" value={c.storage_more} onChange={(v) => set("storage_more", v)} />
            {c.storage_more === "Yes" && (
              <>
                <Txt label="Required capacity" value={c.storage_capacity} onChange={(v) => set("storage_capacity", v)} placeholder="8 TB" />
                <div className="grid sm:grid-cols-2 gap-3">
                  <YN label="Additional HDD required?" value={c.storage_hdd} onChange={(v) => set("storage_hdd", v)} />
                  <YN label="NAS required?" value={c.storage_nas} onChange={(v) => set("storage_nas", v)} />
                </div>
                <Txt label="Other storage notes" value={c.storage_other} onChange={(v) => set("storage_other", v)} />
              </>
            )}
          </Box>

          <Box title="Saudi CCTV compliance">
            <YN label="Is a Saudi CCTV compliance certificate required?" value={c.compliance_required}
              onChange={(v) => set("compliance_required", v)} />
            {c.compliance_required === "Yes" && (
              <>
                <YN label="Is an existing certificate available?" value={c.cert_available}
                  onChange={(v) => set("cert_available", v)} />
                {c.cert_available === "Yes" && (
                  <>
                    <Txt label="Certificate number" value={c.cert_number} onChange={(v) => set("cert_number", v)} />
                    <div className="grid sm:grid-cols-2 gap-3">
                      <Txt label="Issue date" type="date" value={c.cert_issue} onChange={(v) => set("cert_issue", v)} />
                      <Txt label="Expiry date" type="date" value={c.cert_expiry} onChange={(v) => set("cert_expiry", v)} />
                    </div>
                    <Field label="Certificate status">
                      <Pills options={["Valid", "Expired", "Under Renewal"]} value={c.cert_status}
                        onChange={(v) => set("cert_status", v)} />
                    </Field>
                  </>
                )}
                <Field label="Is the system compliant with Saudi requirements?">
                  <Pills options={["Yes", "No", "Needs Inspection"]} value={c.compliant}
                    onChange={(v) => set("compliant", v)} />
                </Field>
                {c.compliant !== "Yes" && (
                  <>
                    <Field label="Compliance issues / corrections required">
                      <textarea rows="3" className={input} value={c.compliance_issues}
                        onChange={(e) => set("compliance_issues", e.target.value)}
                        placeholder="Coverage gaps, retention below requirement, no timestamp overlay…" />
                    </Field>
                    <Field label="Remarks">
                      <textarea rows="2" className={input} value={c.compliance_remarks}
                        onChange={(e) => set("compliance_remarks", e.target.value)} />
                    </Field>
                    <PhotoStrip label="Compliance photos" photos={c.compliance_photos}
                      onChange={(v) => set("compliance_photos", v)} />
                  </>
                )}
              </>
            )}
          </Box>

          <Box title="CCTV cabling">
            <YN label="Is CCTV cabling available?" value={c.cabling} onChange={(v) => set("cabling", v)} />
            {c.cabling === "Yes" && (
              <>
                <Field label="Cable type">
                  <Pills options={["Cat5e", "Cat6", "Coaxial", "Fiber", "Other"]} value={c.cable_type}
                    onChange={(v) => set("cable_type", v)} />
                </Field>
                <Field label="Cable condition">
                  <Pills options={["Good", "Average", "Poor", "Damaged"]} value={c.cable_condition}
                    onChange={(v) => set("cable_condition", v)} />
                </Field>
                <YN label="Cable replacement required?" value={c.cable_replace}
                  onChange={(v) => set("cable_replace", v)} />
              </>
            )}
          </Box>

          <Box title="Power and UPS">
            <YN label="Is a CCTV UPS available?" value={c.ups} onChange={(v) => set("ups", v)} />
            {c.ups === "Yes" && (
              <>
                <div className="grid sm:grid-cols-2 gap-3">
                  <Txt label="UPS capacity" value={c.ups_capacity} onChange={(v) => set("ups_capacity", v)} placeholder="1 kVA" />
                  <Txt label="Backup time" value={c.ups_backup} onChange={(v) => set("ups_backup", v)} placeholder="30 min" />
                </div>
                <YN label="UPS working properly?" value={c.ups_working} onChange={(v) => set("ups_working", v)} />
              </>
            )}
            <YN label="Is a CCTV power supply available?" value={c.power_supply}
              onChange={(v) => set("power_supply", v)} />
            {c.power_supply === "Yes" && (
              <Field label="Power supply condition">
                <Pills options={["Good", "Average", "Poor"]} value={c.power_condition}
                  onChange={(v) => set("power_condition", v)} />
              </Field>
            )}
          </Box>

          <Box title="Condition of each existing camera">
            <p className="text-sm text-ink-faint">
              Add a row per camera. This is what the installer and the quotation are built from.
            </p>
            {c.cameras.map((cam, i) => (
              <CameraRow key={i} cam={cam} index={i}
                onChange={(v) => setCam(i, v)} onRemove={() => delCam(i)} />
            ))}
            <Btn kind="soft" onClick={addCam}>{Icons.plus} Add camera</Btn>
          </Box>

          <Box title="AMC requirement">
            <YN label="Is an AMC required?" value={c.amc} onChange={(v) => set("amc", v)} />
            {c.amc === "Yes" && (
              <>
                <Field label="AMC type">
                  <Pills value={c.amc_type} onChange={(v) => set("amc_type", v)}
                    options={["Comprehensive AMC", "Non-Comprehensive AMC", "Preventive Maintenance",
                              "Corrective Maintenance", "On-Call Support"]} />
                </Field>
                <Field label="AMC period">
                  <Pills options={["1 Month", "3 Months", "6 Months", "1 Year", "Other"]}
                    value={c.amc_period} onChange={(v) => set("amc_period", v)} />
                </Field>
                {c.amc_period === "Other" && <Txt label="Period (specify)" value={c.amc_period_other}
                  onChange={(v) => set("amc_period_other", v)} />}
                <Field label="Maintenance frequency">
                  <Pills options={["Monthly", "Quarterly", "Half-Yearly", "Yearly", "As Required"]}
                    value={c.amc_frequency} onChange={(v) => set("amc_frequency", v)} />
                </Field>
                <Field label="Response time required">
                  <Pills options={["2 Hours", "4 Hours", "8 Hours", "24 Hours", "Next Business Day"]}
                    value={c.amc_response} onChange={(v) => set("amc_response", v)} />
                </Field>
              </>
            )}
          </Box>
        </>
      )}
    </section>
  );
}

const BLANK_CAMERA = {
  no: "", location: "", type: "Dome", brand: "", model: "", resolution: "4MP",
  working: "Yes", image_quality: "Good", night_vision: "Working",
  cable: "Good", power: "OK", remarks: "", photo: "",
};

const BLANK_MAINT = {
  category: "CCTV", issue: "", action_taken: "", solved: "No",
  visit_date: new Date().toISOString().slice(0, 10),
};

const BLANK_SURVEY = {
  client_id: "", client_name: "", contact_person: "", phone: "", client_whatsapp: "", client_email: "", site_address: "",
  job_type: "New CCTV", power_available: "Yes", mounting_surface: [], tools_needed: [],
  requirements: "", latitude: null, longitude: null, photos: [],
  cctv: { ...BLANK_CCTV }, maint: { ...BLANK_MAINT }, technician_id: "",
  handoff_notes: "", status: "Ready for Install",
};

const SURVEY_STATUSES = ["Ready for Install", "Need Quotation", "Missing Materials", "Blocked"];
const PHOTO_LABELS = ["Rack Location", "Camera Angle 1", "Power Source", "Cable Route", "Site Entrance"];

function SurveyForm({ initial, me, onCancel, onDone }) {
  // A form is easier to fill in as one column, so this stays narrow on purpose
  // even though the rest of the app now uses the full width.
  const [f, setF] = useState(() => ({
    ...BLANK_SURVEY, ...initial,
    mounting_surface: typeof initial?.mounting_surface === "string"
      ? initial.mounting_surface.split(",").filter(Boolean) : initial?.mounting_surface || [],
    tools_needed: typeof initial?.tools_needed === "string"
      ? initial.tools_needed.split(",").filter(Boolean) : initial?.tools_needed || [],
    photos: initial?.photos || [],
    cctv: (() => {
      const c = { ...BLANK_CCTV, ...(initial?.cctv || {}) };
      // Surveys saved before this section changed kept a single retention_days value.
      if (!c.recording_days && initial?.cctv?.retention_days) {
        c.recording_days = String(initial.cctv.retention_days);
        c.recording_required = "Yes";
      }
      if (!Array.isArray(c.storage_options)) c.storage_options = [];
      return c;
    })(),
    maint: { ...BLANK_MAINT, ...(initial?.maint || {}) },
    technician_id: initial?.technician_id || "",
    client_id: initial?.client_id || "",
  }));
  const [gps, setGps] = useState("");
  const [busy, setBusy] = useState(false);
  const [clients, setClients] = useState([]);
  const [techs, setTechs] = useState([]);
  const [signing, setSigning] = useState(false);
  const [draftSaved, setDraftSaved] = useState("");
  const set = (k, v) => setF((p) => ({ ...p, [k]: v }));
  const setC = (k, v) => setF((p) => ({ ...p, cctv: { ...p.cctv, [k]: v } }));
  const setM = (k, v) => setF((p) => ({ ...p, maint: { ...p.maint, [k]: v } }));

  useEffect(() => {
    api("/clients").then(setClients).catch(() => {});
    api("/technicians").then(setTechs).catch(() => {});
  }, []);

  // Signal inside industrial buildings is unreliable, so the form keeps a local
  // copy as it's filled in. Nothing is lost if the page reloads or the app dies.
  const draftKey = "rits_draft_" + (initial?.id || "new");
  useEffect(() => {
    const t = setTimeout(() => {
      try {
        localStorage.setItem(draftKey, JSON.stringify({ at: Date.now(), data: f }));
        setDraftSaved(new Date().toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit" }));
      } catch { /* storage full or blocked */ }
    }, 800);
    return () => clearTimeout(t);
  }, [f, draftKey]);

  const clearDraft = () => { try { localStorage.removeItem(draftKey); } catch {} };

  const tech = techs.find((t) => String(t.id) === String(f.technician_id));

  // Picking a saved client copies their details in, so nothing is retyped on site
  // and the reminder system always has an email address to work with.
  function pickClient(id) {
    const c = clients.find((x) => String(x.id) === String(id));
    if (!c) return setF((p) => ({ ...p, client_id: "" }));
    setF((p) => ({
      ...p, client_id: c.id, client_name: c.name,
      contact_person: c.contact_person || p.contact_person,
      phone: c.phone || p.phone,
      client_whatsapp: c.whatsapp || c.phone || p.client_whatsapp,
      client_email: c.email || p.client_email,
      site_address: c.site_address || p.site_address,
    }));
  }

  const assignmentToCustomer = () =>
    `Dear ${f.contact_person || f.client_name},\n\n` +
    `Reliable iT has assigned an engineer for your site survey` +
    (f.site_address ? ` at ${f.site_address}` : "") + `.\n\n` +
    `Engineer: ${tech?.name || "-"}\n` +
    `Contact: ${tech?.phone || "-"}\n` +
    `Email: ${tech?.email || "-"}\n\n` +
    `He will contact you to confirm the visit timing.\n\n` +
    `Reliable iT — iT Service & Solution, Al Jubail`;

  const assignmentToTechnician = () =>
    `Site survey assignment\n\n` +
    `Client: ${f.client_name}\n` +
    `Contact: ${f.contact_person || "-"} ${f.phone || ""}\n` +
    `Email: ${f.client_email || "-"}\n` +
    `Address: ${f.site_address || "-"}\n` +
    `Job type: ${f.job_type}\n` +
    (f.requirements ? `Requirement: ${f.requirements}\n` : "") +
    `\nReliable iT`;

  function tagLocation() {
    setGps("Getting location…");
    navigator.geolocation.getCurrentPosition(
      (pos) => {
        setF((p) => ({ ...p, latitude: +pos.coords.latitude.toFixed(6), longitude: +pos.coords.longitude.toFixed(6) }));
        setGps("");
      },
      () => setGps("Location blocked. Allow it in your browser settings."),
      { enableHighAccuracy: true, timeout: 15000 }
    );
  }

  async function addPhotos(e) {
    const files = Array.from(e.target.files || []);
    for (const file of files) {
      const data = await shrinkImage(file);
      const { url } = await api("/upload", { method: "POST", body: { filename: file.name, data } });
      setF((p) => ({ ...p, photos: [...p.photos, { url, label: PHOTO_LABELS[p.photos.length % PHOTO_LABELS.length] }] }));
    }
    e.target.value = "";
  }

  function missingFields() {
    const m = [];
    if (!String(f.client_name || "").trim()) m.push("Client name");
    if (!String(f.client_whatsapp || "").trim()) m.push("WhatsApp number");
    if (!String(f.client_email || "").trim()) m.push("Client email");
    if (f.job_type === "New CCTV" && f.cctv.recording_required === "Yes"
        && !String(f.cctv.recording_days || "").trim()) {
      m.push("Number of recording days");
    }
    return m;
  }

  async function save() {
    const missing = missingFields();
    if (missing.length) {
      window.scrollTo({ top: 0, behavior: "smooth" });
      return alert("Fill these in before saving:\n\n• " + missing.join("\n• "));
    }
    setBusy(true);
    try {
      if (f.id) await api("/surveys/" + f.id, { method: "PUT", body: f });
      else await api("/surveys", { method: "POST", body: f });
      clearDraft();
      onDone();
    } catch (e) { alert(e.message); setBusy(false); }
  }

  return (
    <div className="pb-28 space-y-6 max-w-3xl">
      <div className="flex items-center gap-2">
        <IconBtn icon={Icons.back} onClick={onCancel} title="Back" />
        <div>
          <h2 className="text-xl font-semibold text-ink">{f.id ? "Edit survey" : "New site survey"}</h2>
          {f.survey_no && <p className="text-xs font-mono text-brand-600">{f.survey_no}</p>}
        </div>
      </div>

      {missingFields().length > 0 && (
        <div className="rounded-xl border border-amber-200 bg-amber-50 p-3">
          <p className="text-sm text-amber-800">
            Still needed before this can be saved: {missingFields().join(", ")}.
          </p>
        </div>
      )}

      <section className="space-y-3">
        <h3 className="text-sm font-semibold text-brand-600">Client & job</h3>
        <Field label="Existing client" hint="Pick one to fill everything in, or leave blank for a new enquiry">
          <select className={input} value={f.client_id || ""} onChange={(e) => pickClient(e.target.value)}>
            <option value="">New / walk-in client</option>
            {clients.map((c) => <option key={c.id} value={c.id}>{c.name}</option>)}
          </select>
        </Field>
        <Field label="Client name" required><input className={input} value={f.client_name}
          onChange={(e) => set("client_name", e.target.value)} /></Field>
        <div className="grid sm:grid-cols-2 gap-3">
          <Field label="Contact person"><input className={input} value={f.contact_person || ""}
            onChange={(e) => set("contact_person", e.target.value)} /></Field>
          <Field label="Phone"><input type="tel" className={input} value={f.phone || ""}
            onChange={(e) => set("phone", e.target.value)} /></Field>
        </div>
        <div className="grid sm:grid-cols-2 gap-3">
          <Field label="WhatsApp number" hint="With country code" required>
            <input type="tel" className={input} value={f.client_whatsapp || ""}
              placeholder="+9665XXXXXXXX"
              onChange={(e) => set("client_whatsapp", e.target.value)} /></Field>
          <Field label="Client email" hint="Quotations and reminders go here" required>
            <input type="email" className={input} value={f.client_email || ""}
              onChange={(e) => set("client_email", e.target.value)} /></Field>
        </div>
        <Field label="Site address"><textarea rows="2" className={input} value={f.site_address || ""}
          onChange={(e) => set("site_address", e.target.value)} /></Field>
        <Field label="Job type">
          <Pills options={["New CCTV", "New Networking", "Maintenance"]} value={f.job_type}
            onChange={(v) => set("job_type", v)} />
        </Field>
      </section>

      {f.job_type === "Maintenance" && (
        <section className="space-y-4 border-t border-gray-200 pt-5">
          <h3 className="text-sm font-semibold text-brand-600">Maintenance call</h3>
          <p className="text-sm text-ink-faint">
            Saving this puts the job on the Maintenance visits page, where the completion
            report and customer signature live.
          </p>

          <Field label="What kind of system?">
            <Pills options={["CCTV", "Networking", "Hardware", "Other"]}
              value={f.maint.category} onChange={(v) => setM("category", v)} />
          </Field>

          <Field label="Issue reported" hint="In the customer's words, plus what you observed">
            <textarea rows="4" className={input} value={f.maint.issue}
              onChange={(e) => setM("issue", e.target.value)}
              placeholder="Camera 3 at main gate offline since Tuesday. No video on NVR channel 3." />
          </Field>

          <Field label="Action taken">
            <textarea rows="4" className={input} value={f.maint.action_taken}
              onChange={(e) => setM("action_taken", e.target.value)}
              placeholder="Tested cable, found water ingress at the outdoor junction. Re-terminated and sealed." />
          </Field>

          <div className="grid sm:grid-cols-2 gap-3">
            <Field label="Problem solved?">
              <Pills options={["Yes", "No"]} value={f.maint.solved} onChange={(v) => setM("solved", v)} />
            </Field>
            <Field label="Date of visit">
              <input type="date" className={input} value={f.maint.visit_date}
                onChange={(e) => setM("visit_date", e.target.value)} />
            </Field>
          </div>

          <p className={"text-sm rounded-lg p-3 border " + (f.maint.solved === "Yes"
            ? "bg-emerald-50 border-emerald-200 text-emerald-800"
            : "bg-amber-50 border-amber-200 text-amber-800")}>
            {f.maint.solved === "Yes"
              ? "Marked solved — the job will show as completed, and a numbered completion report becomes available."
              : "Not solved — the job stays open on the visits page until someone closes it."}
          </p>
        </section>
      )}

      {f.job_type === "New CCTV" && <CctvQuestionnaire c={f.cctv} set={setC} />}

      <section className="space-y-3 border-t border-gray-200 pt-5">
        <h3 className="text-sm font-semibold text-brand-600">Customer requirements</h3>
        <textarea rows="4" className={input} value={f.requirements || ""}
          onChange={(e) => set("requirements", e.target.value)}
          placeholder="Wants 4 cameras covering cash counter and main gate, 30 days backup, mobile viewing." />
      </section>

      <section className="space-y-3 border-t border-gray-200 pt-5">
        <h3 className="text-sm font-semibold text-brand-600">Location & photos</h3>
        <div className={card + " p-4 flex items-center justify-between gap-3"}>
          <div className="min-w-0">
            {f.latitude
              ? <p className="text-sm text-ink font-mono">{f.latitude}, {f.longitude}</p>
              : <p className="text-sm text-ink-faint">{gps || "Stand at the site entrance and tag."}</p>}
          </div>
          <Btn kind="ghost" onClick={tagLocation}>{Icons.pin} {f.latitude ? "Re-tag" : "Tag GPS"}</Btn>
        </div>

        <label className={card + " w-full min-h-14 border-dashed flex items-center justify-center gap-2 text-sm font-medium text-ink-soft cursor-pointer"}>
          {Icons.cam} Add site photos
          <input type="file" accept="image/*" capture="environment" multiple onChange={addPhotos} className="hidden" />
        </label>

        {f.photos.length > 0 && (
          <div className="grid grid-cols-2 md:grid-cols-3 xl:grid-cols-4 gap-3">
            {f.photos.map((p, i) => (
              <div key={i} className={card + " overflow-hidden"}>
                <div className="relative">
                  <img src={fileUrl(p.url)} className="w-full h-28 object-cover" />
                  <button onClick={() => setF((prev) => ({ ...prev, photos: prev.photos.filter((_, j) => j !== i) }))}
                    className="absolute top-1.5 right-1.5 bg-white/90 rounded-md p-1.5 text-rose-600">
                    {Icons.trash}
                  </button>
                </div>
                <input className="w-full px-2 py-2 text-xs text-ink focus:outline-none" value={p.label || ""}
                  onChange={(e) => setF((prev) => ({
                    ...prev, photos: prev.photos.map((x, j) => j === i ? { ...x, label: e.target.value } : x),
                  }))} />
              </div>
            ))}
          </div>
        )}
      </section>

      <section className="space-y-3 border-t border-gray-200 pt-5">
        <h3 className="text-sm font-semibold text-brand-600">Assigned engineer</h3>
        <Field label="Engineer or technician for this survey">
          <select className={input} value={f.technician_id || ""}
            onChange={(e) => set("technician_id", e.target.value)}>
            <option value="">Not assigned yet</option>
            {techs.map((t) => <option key={t.id} value={t.id}>{t.name}</option>)}
          </select>
        </Field>

        {tech && (
          <div className="rounded-xl border border-gray-200 p-4 space-y-3">
            <div className="text-sm">
              <p className="font-medium text-ink">{tech.name}</p>
              <p className="text-ink-soft">{tech.phone || "no phone on file"}</p>
              <p className="text-ink-soft break-all">{tech.email || "no email on file"}</p>
            </div>
            {(!tech.phone || !tech.email) && (
              <p className="text-xs text-amber-700">
                Add this engineer's phone and email under Settings → Users so they appear in the message.
              </p>
            )}
            <div className="flex flex-wrap gap-2">
              <Btn kind="soft" onClick={() => openWhatsApp(f.client_whatsapp || f.phone, assignmentToCustomer())}>
                {Icons.chat} Tell the customer
              </Btn>
              <Btn kind="soft" onClick={() => openWhatsApp(tech.phone, assignmentToTechnician())}>
                {Icons.chat} Send to engineer
              </Btn>
            </div>
            <p className="text-xs text-ink-faint">
              Opens WhatsApp with the message already written. Check it, then press send.
            </p>
          </div>
        )}
      </section>

      <section className="space-y-3 border-t border-gray-200 pt-5">
        <h3 className="text-sm font-semibold text-brand-600">Customer signature</h3>
        {!f.id ? (
          <p className="text-sm text-ink-faint">Save the survey first, then reopen it to collect the signature.</p>
        ) : signing ? (
          <SignaturePad initialName={f.contact_person}
            onCancel={() => setSigning(false)}
            onSave={async (signature, signed_by) => {
              try {
                await api(`/surveys/${f.id}/sign`, { method: "POST", body: { signature, signed_by } });
                setF((p) => ({ ...p, signature, signed_by, signed_at: new Date().toISOString() }));
                setSigning(false);
              } catch (e) { alert(e.message); }
            }} />
        ) : f.signature ? (
          <div className={card + " p-4"}>
            <img src={f.signature} alt="signature" className="h-20 object-contain" />
            <p className="text-sm text-ink mt-2">{f.signed_by}</p>
            <p className="text-xs text-ink-faint">{f.signed_at ? String(f.signed_at).replace("T", " ").slice(0, 16) : ""}</p>
            <Btn kind="ghost" className="mt-3" onClick={() => setSigning(true)}>Sign again</Btn>
          </div>
        ) : (
          <Btn kind="soft" onClick={() => setSigning(true)}>Collect customer signature</Btn>
        )}
      </section>

      <section className="space-y-3 border-t border-gray-200 pt-5">
        <h3 className="text-sm font-semibold text-brand-600">Hand-off</h3>
        <div className="rounded-xl bg-brand-50 border border-brand-200 p-4">
          <p className="text-sm font-semibold text-brand-700 mb-2">Crucial notes for the next technician</p>
          <textarea rows="3" className={input} value={f.handoff_notes || ""}
            onChange={(e) => set("handoff_notes", e.target.value)}
            placeholder="Bring extra RJ45 connectors. Rack is cramped. Site access only after 4 PM." />
        </div>
        <Field label="Survey status">
          <Pills options={SURVEY_STATUSES} value={f.status} onChange={(v) => set("status", v)} />
        </Field>
      </section>

      {/* z-40 keeps this above the bottom navigation bar, which is z-30 */}
      <div className="fixed bottom-0 inset-x-0 bg-white border-t border-gray-200 p-3 z-40">
        <div className="mx-auto max-w-3xl md:max-w-5xl xl:max-w-7xl">
          {draftSaved && (
            <p className="text-xs text-ink-faint mb-1.5 text-center">
              Draft kept on this device at {draftSaved} — safe to lose signal
            </p>
          )}
          <div className="flex gap-3">
            <Btn kind="ghost" onClick={() => { if (confirm("Discard this survey and the local draft?")) { clearDraft(); onCancel(); } }}>Cancel</Btn>
            <Btn onClick={save} disabled={busy} className="flex-1">{busy ? "Saving…" : "Save survey"}</Btn>
          </div>
        </div>
      </div>
    </div>
  );
}

function Surveys({ me, onFormOpen }) {
  const [rows, setRows] = useState(null);
  const [q, setQ] = useState("");
  const [filter, setFilter] = useState("All");
  const [form, setForm] = useState(null);
  const can = (p) => me.permissions.includes(p);

  const load = useCallback(() => api("/surveys?q=" + encodeURIComponent(q)).then(setRows), [q]);
  useEffect(() => { load(); }, [load]);
  // The bottom navigation would sit on top of the Save button, so hide it while editing.
  useEffect(() => { onFormOpen(!!form); return () => onFormOpen(false); }, [form, onFormOpen]);

  if (form) return <SurveyForm initial={form.data} me={me}
    onCancel={() => setForm(null)} onDone={() => { setForm(null); load(); }} />;

  const list = (rows || []).filter((r) => filter === "All" || r.status === filter);

  async function remove(s) {
    if (!confirm(`Delete the survey for "${s.client_name}"?`)) return;
    await api("/surveys/" + s.id, { method: "DELETE" }); load();
  }

  // Shares the PDF itself through the phone's share sheet.
  function shareReport(s) {
    return sharePdfFile({
      url: `/api/surveys/${s.id}/pdf`,
      filename: `${s.survey_no || "site-survey"}-${(s.client_name || "").replace(/[^a-zA-Z0-9]+/g, "-")}.pdf`,
      title: `Site survey — ${s.client_name}`,
      text: `Dear ${s.contact_person || s.client_name},\n\nPlease find our site survey report attached.\n\nReliable iT — Al Jubail`,
    });
  }

  return (
    <div className="space-y-4">
      <div className="flex items-center justify-between">
        <h2 className="text-xl font-semibold text-ink">Site surveys</h2>
        {can("surveys.create") &&
          <Btn onClick={() => setForm({ data: null })}>{Icons.plus} Survey</Btn>}
      </div>

      <div className="relative">
        <span className="absolute left-3 top-3 text-ink-faint">{Icons.search}</span>
        <input className={input + " pl-9"} placeholder="Search client or address"
          value={q} onChange={(e) => setQ(e.target.value)} />
      </div>

      <Pills options={["All", ...SURVEY_STATUSES]} value={filter} onChange={setFilter} />

      {rows === null ? <p className="text-ink-faint">Loading…</p>
        : list.length === 0 ? <Empty text="No surveys here" sub="Start one when you reach the customer site." />
        : <div className="grid gap-3 md:grid-cols-2 2xl:grid-cols-3">{list.map((s) => (
          <SurveyCard key={s.id} s={s} can={can}
            onEdit={() => setForm({ data: s })} onDelete={() => remove(s)}
            onShare={() => shareReport(s)} />
        ))}</div>}
    </div>
  );
}

function SurveyCard({ s, can, onEdit, onDelete, onShare }) {
  const [open, setOpen] = useState(false);
  return (
    <div className={card + " p-4"}>
      <div className="flex justify-between gap-2">
        <button onClick={() => setOpen(!open)} className="text-left min-w-0 flex-1">
          {s.survey_no && <p className="text-xs font-mono text-brand-600">{s.survey_no}</p>}
          <p className="font-medium text-ink truncate">{s.client_name}</p>
          <p className="text-sm text-ink-faint">
            {s.job_type} · {(s.created_at || "").slice(0, 10)}
            {s.technician_name ? ` · ${s.technician_name}` : ""}
          </p>
        </button>
        <div className="flex items-start gap-1 shrink-0">
          <Badge status={s.status} />
        </div>
      </div>

      {open && (
        <div className="mt-3 space-y-3 text-sm border-t border-gray-100 pt-3">
          {s.site_address && <p className="text-ink-soft">{s.site_address}</p>}
          {s.requirements && (
            <div><p className="text-ink-faint text-xs">Requirements</p><p className="text-ink">{s.requirements}</p></div>
          )}
          {s.handoff_notes && (
            <div className="rounded-lg bg-brand-50 border border-brand-200 p-3">
              <p className="text-brand-700 font-medium text-xs">Notes for the technician</p>
              <p className="text-ink mt-1">{s.handoff_notes}</p>
            </div>
          )}
          {s.cctv && (
            <div className="rounded-lg border border-gray-200 p-3 text-xs text-ink-soft space-y-0.5">
              <p className="font-medium text-ink">CCTV requirement</p>
              <p>{s.cctv.indoor} indoor · {s.cctv.outdoor} outdoor · {s.cctv.camera_tech}</p>
              <p>
                {s.cctv.recording_required === "No" ? "No recording"
                  : `${s.cctv.recording_days || s.cctv.retention_days || "?"} days recording`}
                {" · "}PTZ {s.cctv.ptz} · Audio {s.cctv.audio}
              </p>
              {s.cctv.hdd_available === "Yes" && (
                <p>HDD on site: {s.cctv.hdd_count || 1} × {s.cctv.hdd_capacity || "?"} · health {s.cctv.hdd_health}</p>
              )}
              {s.cctv.extra_storage === "Yes" && (
                <p>Extra storage needed: {s.cctv.extra_capacity || "?"}
                  {(s.cctv.storage_options || []).length ? ` (${s.cctv.storage_options.join(", ")})` : ""}</p>
              )}
              <p>{s.cctv.site_kind} site · Certificate {s.cctv.certificate_required}</p>
              {s.cctv.existing === "Yes" && <p>Existing system: {s.cctv.existing_condition || "details not recorded"}</p>}
            </div>
          )}
          <div className="flex flex-wrap gap-x-4 gap-y-1 text-xs text-ink-faint">
            <span>Power: {s.power_available}</span>
            {s.mounting_surface && <span>Surface: {s.mounting_surface}</span>}
            {s.tools_needed && <span>Tools: {s.tools_needed}</span>}
            {s.client_email && <span>✉ {s.client_email}</span>}
            {s.client_whatsapp && <span>✆ {s.client_whatsapp}</span>}
          </div>
          {s.technician_name && (
            <div className="flex flex-wrap items-center gap-2">
              <span className="text-xs text-ink-faint">
                Assigned to {s.technician_name} · {s.technician_phone || "no phone"}
              </span>
              <button
                onClick={() => openWhatsApp(s.client_whatsapp || s.phone,
                  `Dear ${s.contact_person || s.client_name},\n\nReliable iT has assigned an engineer for your site survey` +
                  (s.site_address ? ` at ${s.site_address}` : "") + `.\n\nEngineer: ${s.technician_name}\n` +
                  `Contact: ${s.technician_phone || "-"}\nEmail: ${s.technician_email || "-"}\n\n` +
                  `He will contact you to confirm the visit timing.\n\nReliable iT — Al Jubail`)}
                className="text-xs text-emerald-700 underline">Share on WhatsApp</button>
            </div>
          )}
          {s.latitude && (
            <a className="text-brand-600 underline text-xs" target="_blank" rel="noreferrer"
               href={`https://maps.google.com/?q=${s.latitude},${s.longitude}`}>Open location in Maps</a>
          )}
          {s.photos?.length > 0 && (
            <div className="grid grid-cols-3 md:grid-cols-4 gap-2">
              {s.photos.map((p) => (
                <a key={p.id} href={fileUrl(p.url)} target="_blank" rel="noreferrer">
                  <img src={fileUrl(p.url)} className="w-full h-20 object-cover rounded-lg" />
                  <p className="text-xs text-ink-faint mt-1 truncate">{p.label}</p>
                </a>
              ))}
            </div>
          )}
        </div>
      )}

      <div className="flex flex-wrap items-center gap-2 mt-3 pt-3 border-t border-gray-100">
        <button onClick={() => setOpen(!open)} className="text-sm text-brand-600">
          {open ? "Hide details" : "Show details"}
        </button>
        <a href={fileUrl(`/api/surveys/${s.id}/pdf`)} target="_blank" rel="noreferrer"
           className="min-h-9 rounded-lg px-3 text-sm font-medium inline-flex items-center gap-1.5 bg-brand-50 text-brand-700">
          {Icons.doc} PDF
        </a>
        <Btn kind="soft" onClick={onShare}
          className="min-h-9 bg-emerald-50 text-emerald-700 hover:bg-emerald-100">
          {Icons.chat} Share PDF
        </Btn>
        {s.signature && <span className="text-xs text-emerald-700">Signed</span>}
        <div className="ml-auto flex gap-1">
          {can("surveys.edit") && <IconBtn icon={Icons.pencil} title="Edit" onClick={onEdit} />}
          {can("surveys.delete") && <IconBtn icon={Icons.trash} title="Delete"
            tone="text-rose-400 hover:text-rose-600" onClick={onDelete} />}
        </div>
      </div>
    </div>
  );
}

/* ============================== visits ============================== */

function Visits({ me }) {
  const [rows, setRows] = useState(null);
  const [filter, setFilter] = useState("Upcoming");
  const [view, setView] = useState("list");
  const [edit, setEdit] = useState(null);
  const [signing, setSigning] = useState(null);
  const [techs, setTechs] = useState([]);
  const [clients, setClients] = useState([]);
  const [contracts, setContracts] = useState([]);
  const [month, setMonth] = useState(() => new Date().toISOString().slice(0, 7));
  const can = (p) => me.permissions.includes(p);

  const load = useCallback(() => api("/visits").then(setRows).catch(() => setRows([])), []);
  useEffect(() => {
    load();
    api("/technicians").then(setTechs).catch(() => {});
    api("/clients").then(setClients).catch(() => {});
    api("/contracts").then(setContracts).catch(() => {});
  }, [load]);

  if (rows === null) return <p className="text-ink-faint">Loading…</p>;

  const counts = rows.reduce((a, v) => ({ ...a, [v.state]: (a[v.state] || 0) + 1 }), {});
  const list = filter === "All" ? rows : rows.filter((v) => v.state === filter);

  const blank = (date) => ({
    scheduled_on: date || new Date().toISOString().slice(0, 10),
    contract_id: "", client_id: "", client_name: "", category: "CCTV",
    issue: "", action_taken: "", solved: "No", report: "", technician_id: "",
    completed: false, source: "manual",
  });

  async function save(v) {
    try {
      if (v.id) {
        await api("/visits/" + v.id, {
          method: "PUT",
          body: { completed: !!v.completed, completed_on: v.completed_on, report: v.report,
                  technician_id: v.technician_id, scheduled_on: v.scheduled_on,
                  category: v.category, issue: v.issue, action_taken: v.action_taken, solved: v.solved },
        });
      } else {
        await api("/visits", { method: "POST", body: v });
      }
      setEdit(null); load();
    } catch (e) { alert(e.message); }
  }

  async function remove(v) {
    if (!confirm(`Delete the visit scheduled for ${fmt(v.scheduled_on)}?`)) return;
    await api("/visits/" + v.id, { method: "DELETE" }); load();
  }

  async function generate() {
    if (!confirm("Create any maintenance visits that are missing from your AMC contracts?\n\nExisting visits are left alone.")) return;
    try {
      const r = await api("/visits/generate", { method: "POST", body: {} });
      alert(r.added === 0
        ? "Nothing was missing — every contract already has its visits."
        : `${r.added} visit(s) created from your contracts.`);
      load();
    } catch (e) { alert(e.message); }
  }

  function shareVisit(v) {
    return sharePdfFile({
      url: `/api/visits/${v.id}/pdf`,
      filename: `${v.visit_no || "maintenance"}-${(v.client_name || "customer").replace(/[^a-zA-Z0-9]+/g, "-")}.pdf`,
      title: `Maintenance report — ${v.client_name}`,
      text: `Dear ${v.client_name},\n\nPlease find our maintenance completion report attached.\n\nReliable iT — Al Jubail`,
    });
  }

  // Month grid. Weeks start on Sunday, as the working week does here.
  const cal = (() => {
    const [y, m] = month.split("-").map(Number);
    const first = new Date(y, m - 1, 1);
    const days = new Date(y, m, 0).getDate();
    const cells = Array(first.getDay()).fill(null);
    for (let d = 1; d <= days; d++) {
      const iso = `${month}-${String(d).padStart(2, "0")}`;
      cells.push({ d, iso, visits: rows.filter((v) => v.scheduled_on === iso) });
    }
    return cells;
  })();

  const shiftMonth = (n) => {
    const [y, m] = month.split("-").map(Number);
    setMonth(new Date(y, m - 1 + n, 1).toISOString().slice(0, 7));
  };
  const todayIso = new Date().toISOString().slice(0, 10);

  return (
    <div className="space-y-4">
      <div className="flex items-start justify-between gap-2">
        <div>
          <h2 className="text-xl font-semibold text-ink">Maintenance visits</h2>
          <p className="text-sm text-ink-faint">
            {counts.Overdue || 0} overdue · {counts.Upcoming || 0} upcoming · {counts.Completed || 0} done
          </p>
        </div>
        {can("contracts.edit") &&
          <Btn onClick={() => setEdit(blank())}>{Icons.plus} Visit</Btn>}
      </div>

      <div className="flex gap-1">
        <Btn kind={view === "list" ? "primary" : "ghost"} onClick={() => setView("list")}>List</Btn>
        <Btn kind={view === "cal" ? "primary" : "ghost"} onClick={() => setView("cal")}>Calendar</Btn>
      </div>

      {rows.length === 0 && (
        <div className={card + " p-5 space-y-2"}>
          <h3 className="font-semibold text-ink">Nothing here yet</h3>
          <p className="text-sm text-ink-soft">
            Visits appear from three places: automatically from each AMC contract's service
            frequency, from a survey where the job type is Maintenance, or added by hand here.
          </p>
          <p className="text-sm text-ink-faint">
            If you have AMC contracts but no visits — after restoring a backup, for instance —
            this fills in the missing ones.
          </p>
          {can("contracts.edit") && <Btn kind="soft" onClick={generate}>Generate from contracts</Btn>}
        </div>
      )}

      {view === "cal" ? (
        <div className={card + " p-3"}>
          <div className="flex items-center justify-between mb-3">
            <Btn kind="ghost" onClick={() => shiftMonth(-1)}>‹ Prev</Btn>
            <p className="font-medium text-ink">
              {new Date(month + "-01T00:00:00").toLocaleDateString("en-GB", { month: "long", year: "numeric" })}
            </p>
            <Btn kind="ghost" onClick={() => shiftMonth(1)}>Next ›</Btn>
          </div>
          <p className="text-xs text-ink-faint mb-2">Tap any day to add a visit, or tap a job to open it.</p>
          <div className="grid grid-cols-7 gap-1">
            {["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"].map((d) => (
              <div key={d} className="text-xs text-ink-faint py-1 text-center">{d.slice(0, 1)}</div>
            ))}
            {cal.map((c, i) => !c ? <div key={i} /> : (
              <div key={i}
                onClick={() => can("contracts.edit") && setEdit(blank(c.iso))}
                className={"min-h-16 md:min-h-24 rounded-lg p-1 border cursor-pointer hover:border-brand-300 " +
                  (c.iso === todayIso ? "border-brand-500 bg-brand-50" : "border-gray-200")}>
                <span className="text-xs text-ink-faint">{c.d}</span>
                {c.visits.map((v) => (
                  <button key={v.id}
                    onClick={(e) => { e.stopPropagation(); setEdit({ ...v, completed: !!v.completed_on }); }}
                    className={"block w-full text-left text-xs rounded px-1 py-0.5 mt-0.5 truncate " +
                      (v.completed_on ? "bg-emerald-100 text-emerald-800"
                        : v.state === "Overdue" ? "bg-rose-100 text-rose-800" : "bg-brand-100 text-brand-800")}>
                    {v.client_name || "Visit"}
                  </button>
                ))}
              </div>
            ))}
          </div>
        </div>
      ) : rows.length > 0 && (
        <>
          <Pills options={["Upcoming", "Overdue", "Completed", "All"]} value={filter} onChange={setFilter} />
          {list.length === 0 ? <Empty text="Nothing in this list" /> :
            <div className="grid gap-3 md:grid-cols-2 2xl:grid-cols-3">{list.map((v) => (
            <div key={v.id} className={card + " p-4"}>
              <div className="flex justify-between gap-2">
                <div className="min-w-0">
                  {v.visit_no && <p className="text-xs font-mono text-brand-600">{v.visit_no}</p>}
                  <p className="font-medium text-ink truncate">{v.client_name || "Unnamed"}</p>
                  <p className="text-sm text-ink-faint truncate">
                    {v.source === "survey" ? `From survey ${v.survey_no || ""}` :
                     v.source === "manual" ? "Added by hand" :
                     `${v.contract_no || "—"} · ${v.frequency || ""}`}
                    {v.category ? ` · ${v.category}` : ""}
                  </p>
                </div>
                <span className={"rounded-md border px-2 py-0.5 text-xs font-medium whitespace-nowrap " +
                  (v.state === "Completed" ? "bg-emerald-50 text-emerald-700 border-emerald-200"
                    : v.state === "Overdue" ? "bg-rose-50 text-rose-700 border-rose-200"
                    : "bg-brand-50 text-brand-700 border-brand-200")}>
                  {v.state}
                </span>
              </div>

              <div className="flex flex-wrap gap-x-4 gap-y-1 mt-2 text-xs text-ink-faint">
                <span>Due {fmt(v.scheduled_on)}</span>
                {!v.completed_on && <span>{v.days_away < 0 ? `${Math.abs(v.days_away)} days late` : `in ${v.days_away} days`}</span>}
                {v.completed_on && <span>Done {fmt(v.completed_on)}</span>}
                {v.technician_name && <span>{v.technician_name}</span>}
                {v.solved && <span>Solved: {v.solved}</span>}
                {v.signature && <span className="text-emerald-700">Signed</span>}
              </div>

              {(v.issue || v.action_taken || v.report) && (
                <div className="mt-2 border-t border-gray-100 pt-2 space-y-1 text-sm">
                  {v.issue && <p className="text-ink-soft"><span className="text-ink-faint text-xs">Issue: </span>{v.issue}</p>}
                  {v.action_taken && <p className="text-ink-soft"><span className="text-ink-faint text-xs">Action: </span>{v.action_taken}</p>}
                  {v.report && <p className="text-ink-soft"><span className="text-ink-faint text-xs">Notes: </span>{v.report}</p>}
                </div>
              )}

              <div className="flex flex-wrap items-center gap-2 mt-3">
                {can("contracts.edit") && (
                  <Btn kind="soft" onClick={() => setEdit({ ...v, completed: !!v.completed_on })}>
                    {v.completed_on ? "Edit report" : "Complete"}
                  </Btn>
                )}
                {can("contracts.edit") && (
                  <Btn kind="ghost" onClick={() => setSigning(v)}>
                    {v.signature ? "Sign again" : "Customer signature"}
                  </Btn>
                )}
                {v.completed_on && (
                  <>
                    <a href={fileUrl(`/api/visits/${v.id}/pdf`)} target="_blank" rel="noreferrer"
                       className="min-h-10 rounded-lg px-3 text-sm font-medium inline-flex items-center gap-1.5 bg-brand-50 text-brand-700">
                      {Icons.doc} PDF
                    </a>
                    <Btn kind="soft" onClick={() => shareVisit(v)}
                      className="bg-emerald-50 text-emerald-700 hover:bg-emerald-100">
                      {Icons.chat} Share
                    </Btn>
                  </>
                )}
                <div className="ml-auto">
                  {can("contracts.delete") && <IconBtn icon={Icons.trash} title="Delete"
                    tone="text-rose-400 hover:text-rose-600" onClick={() => remove(v)} />}
                </div>
              </div>
            </div>
          ))}</div>}
        </>
      )}

      {signing && (
        <Modal title="Customer signature" onClose={() => setSigning(null)}>
          <SignaturePad initialName={signing.signed_by}
            onCancel={() => setSigning(null)}
            onSave={async (signature, signed_by) => {
              try {
                await api(`/visits/${signing.id}/sign`, { method: "POST", body: { signature, signed_by } });
                setSigning(null); load();
              } catch (e) { alert(e.message); }
            }} />
        </Modal>
      )}

      {edit && (
        <Modal title={edit.id ? "Maintenance visit" : "New visit"} onClose={() => setEdit(null)} wide>
          <div className="space-y-3">
            {edit.id ? (
              <p className="text-sm text-ink-soft">{edit.client_name} {edit.contract_no ? `· ${edit.contract_no}` : ""}</p>
            ) : (
              <>
                <Field label="AMC contract" hint="Leave blank for a one-off call with no contract">
                  <select className={input} value={edit.contract_id || ""}
                    onChange={(e) => setEdit({ ...edit, contract_id: e.target.value, source: e.target.value ? "contract" : "manual" })}>
                    <option value="">No contract</option>
                    {contracts.map((c) => (
                      <option key={c.id} value={c.id}>{c.client_name} — {c.contract_no || "no number"}</option>
                    ))}
                  </select>
                </Field>
                {!edit.contract_id && (
                  <Field label="Client">
                    <select className={input} value={edit.client_id || ""}
                      onChange={(e) => setEdit({ ...edit, client_id: e.target.value })}>
                      <option value="">Type a name below instead</option>
                      {clients.map((c) => <option key={c.id} value={c.id}>{c.name}</option>)}
                    </select>
                  </Field>
                )}
                {!edit.contract_id && !edit.client_id && (
                  <Field label="Client name">
                    <input className={input} value={edit.client_name || ""}
                      onChange={(e) => setEdit({ ...edit, client_name: e.target.value })} />
                  </Field>
                )}
              </>
            )}

            <div className="grid sm:grid-cols-2 gap-3">
              <Field label="Scheduled date">
                <input type="date" className={input} value={edit.scheduled_on || ""}
                  onChange={(e) => setEdit({ ...edit, scheduled_on: e.target.value })} />
              </Field>
              <Field label="Engineer">
                <select className={input} value={edit.technician_id || ""}
                  onChange={(e) => setEdit({ ...edit, technician_id: e.target.value })}>
                  <option value="">Not recorded</option>
                  {techs.map((t) => <option key={t.id} value={t.id}>{t.name}</option>)}
                </select>
              </Field>
            </div>

            <Field label="Kind of system">
              <Pills options={["CCTV", "Networking", "Hardware", "Other"]} value={edit.category || "CCTV"}
                onChange={(v) => setEdit({ ...edit, category: v })} />
            </Field>
            <Field label="Issue reported">
              <textarea rows="3" className={input} value={edit.issue || ""}
                onChange={(e) => setEdit({ ...edit, issue: e.target.value })} />
            </Field>
            <Field label="Action taken">
              <textarea rows="3" className={input} value={edit.action_taken || ""}
                onChange={(e) => setEdit({ ...edit, action_taken: e.target.value })} />
            </Field>
            <Field label="Problem solved?">
              <Pills options={["Yes", "No"]} value={edit.solved || "No"}
                onChange={(v) => setEdit({ ...edit, solved: v })} />
            </Field>

            <label className="flex items-center gap-3 rounded-lg border border-gray-200 p-3">
              <input type="checkbox" className="w-5 h-5 accent-brand-500" checked={!!edit.completed}
                onChange={(e) => setEdit({ ...edit, completed: e.target.checked })} />
              <span className="text-sm text-ink-soft">Visit completed — gives it a report number</span>
            </label>
            {edit.completed && (
              <Field label="Completed on">
                <input type="date" className={input}
                  value={edit.completed_on || new Date().toISOString().slice(0, 10)}
                  onChange={(e) => setEdit({ ...edit, completed_on: e.target.value })} />
              </Field>
            )}
            <Field label="Engineer's notes">
              <textarea rows="3" className={input} value={edit.report || ""}
                onChange={(e) => setEdit({ ...edit, report: e.target.value })}
                placeholder="Anything the customer or the next engineer should know." />
            </Field>

            <div className="flex gap-2 pt-1">
              <Btn kind="ghost" onClick={() => setEdit(null)}>Cancel</Btn>
              <Btn onClick={() => save(edit)} className="flex-1">Save visit</Btn>
            </div>
          </div>
        </Modal>
      )}
    </div>
  );
}

/* =============================== zatca =============================== */

const ZATCA_TONE = {
  "Pending": "bg-gray-100 text-ink-soft border-gray-200",
  "Submitted": "bg-brand-50 text-brand-700 border-brand-200",
  "In Progress": "bg-amber-50 text-amber-700 border-amber-200",
  "Completed": "bg-emerald-50 text-emerald-700 border-emerald-200",
};

function Zatca({ me }) {
  const [rows, setRows] = useState(null);
  const [q, setQ] = useState("");
  const [filter, setFilter] = useState("All");
  const [open, setOpen] = useState(null);
  const [creating, setCreating] = useState(null);
  const [clients, setClients] = useState([]);
  const can = (p) => me.permissions.includes(p);

  const load = useCallback(() =>
    api(`/zatca?q=${encodeURIComponent(q)}`).then(setRows).catch(() => setRows([])), [q]);
  useEffect(() => { load(); }, [load]);
  useEffect(() => { if (can("zatca.manage")) api("/clients").then(setClients).catch(() => {}); }, []);

  if (rows === null) return <p className="text-ink-faint">Loading…</p>;
  const list = filter === "All" ? rows : rows.filter((r) => r.status === filter);
  const counts = rows.reduce((a, r) => ({ ...a, [r.status]: (a[r.status] || 0) + 1 }), {});

  async function create(c) {
    try {
      const r = await api("/zatca", { method: "POST", body: c });
      setCreating(null); load(); setOpen(r);
    } catch (e) { alert(e.message); }
  }

  async function remove(r) {
    if (!confirm(`Delete the ZATCA request for ${r.company_name || r.client_name}?`)) return;
    await api("/zatca/" + r.id, { method: "DELETE" }); load(); setOpen(null);
  }

  return (
    <div className="space-y-4">
      <div className="flex items-start justify-between gap-2">
        <div>
          <h2 className="text-xl font-semibold text-ink">ZATCA Phase 2</h2>
          <p className="text-sm text-ink-faint">
            {counts.Pending || 0} waiting on the customer · {counts.Submitted || 0} submitted ·
            {" "}{counts["In Progress"] || 0} in progress · {counts.Completed || 0} done
          </p>
        </div>
        {can("zatca.manage") &&
          <Btn onClick={() => setCreating({ client_id: "", client_name: "" })}>{Icons.plus} Request</Btn>}
      </div>

      <div className="relative">
        <span className="absolute left-3 top-3 text-ink-faint">{Icons.search}</span>
        <input className={input + " pl-9"} placeholder="Search company, CR or VAT number"
          value={q} onChange={(e) => setQ(e.target.value)} />
      </div>
      <Pills options={["All", "Pending", "Submitted", "In Progress", "Completed"]}
        value={filter} onChange={setFilter} />

      {list.length === 0 ? (
        <Empty text="Nothing here yet"
          sub={can("zatca.manage")
            ? "Create a request, then send the customer the link or QR code."
            : "Submissions from customers will appear here."} />
      ) : (
        <div className="grid gap-3 md:grid-cols-2 2xl:grid-cols-3">
          {list.map((r) => (
            <button key={r.id} onClick={() => setOpen(r)} className={card + " p-4 text-left"}>
              <div className="flex justify-between gap-2">
                <div className="min-w-0">
                  <p className="text-xs font-mono text-brand-600">{r.ref_no}</p>
                  <p className="font-medium text-ink truncate">{r.company_name || r.client_name}</p>
                  <p className="text-sm text-ink-faint truncate">
                    {r.vat_number ? `VAT ${r.vat_number}` : "no VAT yet"}
                    {r.cr_number ? ` · CR ${r.cr_number}` : ""}
                  </p>
                </div>
                <span className={"rounded-md border px-2 py-0.5 text-xs font-medium whitespace-nowrap " +
                  (ZATCA_TONE[r.status] || "")}>{r.status}</span>
              </div>
              <div className="flex flex-wrap gap-x-4 gap-y-1 mt-2 text-xs text-ink-faint">
                {r.submitted_at && <span>Submitted {(r.submitted_at || "").slice(0, 10)}</span>}
                {r.invoicing_software && <span>{r.invoicing_software}</span>}
                {r.has_otp && !r.otp_expired && <span className="text-emerald-700">Code ready</span>}
                {r.has_otp && r.otp_expired && <span className="text-rose-700">Code expired</span>}
                {r.has_legacy_credentials && <span className="text-amber-700">Legacy credentials</span>}
              </div>
            </button>
          ))}
        </div>
      )}

      {creating && (
        <Modal title="New ZATCA onboarding request" onClose={() => setCreating(null)}>
          <div className="space-y-3">
            <p className="text-sm text-ink-soft">
              This creates a link and a QR code to send the customer. They fill in their own
              details — no login, nothing to install.
            </p>
            <Field label="Client">
              <select className={input} value={creating.client_id}
                onChange={(e) => setCreating({ ...creating, client_id: e.target.value })}>
                <option value="">Not an existing client</option>
                {clients.map((c) => <option key={c.id} value={c.id}>{c.name}</option>)}
              </select>
            </Field>
            {!creating.client_id && (
              <Field label="Company name" required>
                <input className={input} value={creating.client_name}
                  onChange={(e) => setCreating({ ...creating, client_name: e.target.value })} />
              </Field>
            )}
            <div className="flex gap-2 pt-1">
              <Btn kind="ghost" onClick={() => setCreating(null)}>Cancel</Btn>
              <Btn onClick={() => create(creating)} className="flex-1">Create link</Btn>
            </div>
          </div>
        </Modal>
      )}

      {open && <ZatcaDetail r={open} me={me} onClose={() => setOpen(null)}
        onChanged={load} onDelete={() => remove(open)} />}
    </div>
  );
}

function ZatcaDetail({ r: initial, me, onClose, onChanged, onDelete }) {
  const [r, setR] = useState(initial);
  const [secrets, setSecrets] = useState(null);
  const [showQr, setShowQr] = useState(false);
  const can = (p) => me.permissions.includes(p);
  const link = r.link || (location.origin + "/z/" + r.token);

  useEffect(() => { api("/zatca/" + initial.id).then((d) => setR({ ...d, link: initial.link })).catch(() => {}); },
    [initial.id]);

  async function setStatus(status) {
    try {
      const d = await api("/zatca/" + r.id, { method: "PUT", body: { status, internal_notes: r.internal_notes } });
      setR({ ...d, link }); onChanged();
    } catch (e) { alert(e.message); }
  }

  async function reveal() {
    const password = prompt("Confirm your password to view the onboarding code.\n\nThis is recorded in the audit log.");
    if (!password) return;
    try {
      const d = await api(`/zatca/${r.id}/reveal`, { method: "POST", body: { password } });
      setSecrets(d);
    } catch (e) { alert(e.message); }
  }

  // Reopens the customer's link so they can send a fresh code.
  async function askNewOtp() {
    if (!confirm("Reopen this customer's form so they can submit a new OTP?")) return;
    try {
      const d = await api(`/zatca/${r.id}/request-otp`, { method: "POST", body: {} });
      const fresh = await api("/zatca/" + r.id);
      setR({ ...fresh, link }); setSecrets(null); onChanged();
      openWhatsApp(r.contact_mobile, d.message);
    } catch (e) { alert(e.message); }
  }

  async function purge() {
    if (!confirm("Delete the stored onboarding code?\n\nEverything else is kept. This cannot be undone.")) return;
    try {
      const d = await api(`/zatca/${r.id}/purge`, { method: "POST", body: {} });
      alert(d.message); setSecrets(null);
      const fresh = await api("/zatca/" + r.id); setR({ ...fresh, link }); onChanged();
    } catch (e) { alert(e.message); }
  }

  const Row = ({ label, value }) => (
    <div className="flex gap-3 py-1.5 border-t border-gray-100 first:border-0">
      <span className="text-xs text-ink-faint w-40 shrink-0 pt-0.5">{label}</span>
      <span className="text-sm text-ink break-words min-w-0">{value || "—"}</span>
    </div>
  );

  const address = [r.building_no, r.street, r.district, r.city, r.postal_code, r.additional_no]
    .filter(Boolean).join(", ");

  return (
    <Modal title={r.company_name || r.client_name} onClose={onClose} wide>
      <div className="space-y-4">
        <div className="flex flex-wrap items-center gap-2">
          <span className={"rounded-md border px-2 py-0.5 text-xs font-medium " + (ZATCA_TONE[r.status] || "")}>
            {r.status}
          </span>
          <span className="text-xs font-mono text-brand-600">{r.ref_no}</span>
        </div>

        {r.status === "Pending" && can("zatca.manage") && (
          <div className="rounded-xl border border-brand-200 bg-brand-50 p-4 space-y-3">
            <p className="text-sm text-brand-800">
              Waiting for the customer. Send them this link, or let them scan the QR code.
            </p>
            <div className="flex gap-2">
              <input className={input + " font-mono text-xs"} readOnly value={link}
                onFocus={(e) => e.target.select()} />
              <Btn kind="ghost" onClick={() => {
                navigator.clipboard?.writeText(link);
                alert("Link copied.");
              }}>Copy</Btn>
            </div>
            <div className="flex flex-wrap gap-2">
              <Btn kind="soft" onClick={() => openWhatsApp(r.contact_mobile,
                `Dear ${r.company_name || r.client_name},\n\n` +
                `To complete your ZATCA Phase 2 e-invoicing integration, please fill in this short form:\n${link}\n\n` +
                `Reliable iT — Al Jubail`)}>{Icons.chat} Send on WhatsApp</Btn>
              <Btn kind="ghost" onClick={() => setShowQr(!showQr)}>{showQr ? "Hide" : "Show"} QR code</Btn>
            </div>
            {showQr && (
              <div className="bg-white rounded-xl p-4 inline-block">
                <img src={fileUrl(`/api/zatca/${r.id}/qr`)} alt="QR code" className="w-48 h-48" />
                <p className="text-xs text-ink-faint mt-2 text-center">Scan to open the form</p>
              </div>
            )}
          </div>
        )}

        <div className={card + " p-4"}>
          <h4 className="text-sm font-semibold text-brand-600 mb-2">Company</h4>
          <Row label="Company name" value={r.company_name} />
          <Row label="CR number" value={r.cr_number} />
          <Row label="VAT number" value={r.vat_number} />
          <Row label="Contact person" value={r.contact_person} />
          <Row label="Mobile" value={r.contact_mobile} />
          <Row label="Email" value={r.contact_email} />
          <Row label="ZATCA registered mobile" value={r.zatca_mobile} />
        </div>

        <div className={card + " p-4"}>
          <h4 className="text-sm font-semibold text-brand-600 mb-2">National address</h4>
          <Row label="Full address" value={address} />
          <Row label="Building / additional" value={[r.building_no, r.additional_no].filter(Boolean).join(" / ")} />
          <Row label="Postal code" value={r.postal_code} />
        </div>

        <div className={card + " p-4"}>
          <h4 className="text-sm font-semibold text-brand-600 mb-2">Invoicing system</h4>
          <Row label="Software" value={r.invoicing_software} />
          <Row label="Version" value={r.erp_version} />
          <Row label="Branches" value={r.branch_count} />
          <Row label="Billing devices" value={r.device_count} />
          <Row label="Customer notes" value={r.notes} />
        </div>

        <div className={card + " p-4"}>
          <h4 className="text-sm font-semibold text-brand-600 mb-2">Onboarding OTP</h4>
          <p className="text-xs text-ink-faint mb-2">
            We never hold the customer's ZATCA portal password. Onboarding uses a one-time code
            that expires about an hour after they generate it.
          </p>
          {r.credentials_purged ? (
            <p className="text-sm text-emerald-700">Deleted after integration. Nothing sensitive is stored.</p>
          ) : !r.has_otp ? (
            <p className="text-sm text-ink-faint">No code submitted yet.</p>
          ) : (
            <>
              {r.otp_expired ? (
                <p className="text-sm text-rose-700">
                  This code arrived {Math.floor((r.otp_age_minutes || 0) / 60)}h ago, so it will have
                  expired. Ask the customer for a fresh one.
                </p>
              ) : (
                <p className="text-sm text-emerald-700">
                  Code received {r.otp_age_minutes} minute{r.otp_age_minutes === 1 ? "" : "s"} ago —
                  use it soon.
                </p>
              )}
              {secrets ? (
                <>
                  <Row label="OTP" value={<span className="font-mono text-lg">{secrets.otp || "—"}</span>} />
                  {(secrets.legacy_user || secrets.legacy_pass) && (
                    <>
                      <p className="text-xs text-amber-700 mt-2">
                        This older record still holds portal credentials from before we stopped
                        collecting them. Delete them once you no longer need them.
                      </p>
                      <Row label="Legacy user ID" value={<span className="font-mono">{secrets.legacy_user}</span>} />
                      <Row label="Legacy password" value={<span className="font-mono">{secrets.legacy_pass}</span>} />
                    </>
                  )}
                  <p className="text-xs text-ink-faint mt-2">This view was recorded in the audit log.</p>
                </>
              ) : can("zatca.secrets") ? (
                <Btn kind="ghost" className="mt-2" onClick={reveal}>{Icons.shield} Show the code</Btn>
              ) : (
                <p className="text-xs text-ink-faint mt-1">You don't have permission to view the code.</p>
              )}
            </>
          )}
          {can("zatca.manage") && (
            <div className="flex flex-wrap gap-2 mt-3">
              <Btn kind="soft" onClick={askNewOtp}>Ask for a new code</Btn>
              {(r.has_otp || r.has_legacy_credentials) && !r.credentials_purged &&
                <Btn kind="danger" onClick={purge}>Delete stored code</Btn>}
            </div>
          )}
          {r.has_legacy_credentials && !r.credentials_purged && (
            <p className="text-xs text-amber-700 mt-2">
              Holds portal credentials from an earlier version — worth deleting.
            </p>
          )}
        </div>

        {(can("zatca.progress") || can("zatca.manage")) && (
          <div className={card + " p-4 space-y-3"}>
            <h4 className="text-sm font-semibold text-brand-600">Progress</h4>
            <Pills options={["Pending", "Submitted", "In Progress", "Completed"]}
              value={r.status} onChange={setStatus} />
            <Field label="Internal notes">
              <textarea rows="3" className={input} value={r.internal_notes || ""}
                onChange={(e) => setR({ ...r, internal_notes: e.target.value })}
                onBlur={() => setStatus(r.status)}
                placeholder="CSID issued, device onboarded, waiting on customer…" />
            </Field>
            {can("zatca.manage") &&
              <Btn kind="danger" onClick={onDelete}>Delete this request</Btn>}
          </div>
        )}
      </div>
    </Modal>
  );
}

/* ============================== settings ============================== */

function Settings({ me, reloadMe }) {
  const [tab, setTab] = useState("users");
  const tabs = [["users", "Users"], ["company", "Company"], ["notify", "Notifications"],
                ["activity", "Activity"], ["audit", "Audit log"], ["data", "Data"]];
  return (
    <div className="space-y-4">
      <h2 className="text-xl font-semibold text-ink">Settings</h2>
      <div className="flex gap-2 overflow-x-auto pb-1">
        {tabs.map(([id, label]) => (
          <button key={id} onClick={() => setTab(id)}
            className={"min-h-10 rounded-lg px-3.5 text-sm font-medium border whitespace-nowrap " +
              (tab === id ? "bg-brand-500 text-white border-brand-500" : "bg-white text-ink-soft border-gray-300")}>
            {label}
          </button>
        ))}
      </div>
      {tab === "users" && <UsersTab me={me} />}
      {tab === "company" && <SettingsForm section="company" />}
      {tab === "notify" && <SettingsForm section="notify" />}
      {tab === "activity" && <ActivityTab />}
      {tab === "audit" && <AuditTab />}
      {tab === "data" && <DataTab />}
    </div>
  );
}

/**
 * Ready-made permission sets. Ticking sixteen boxes correctly is a job in
 * itself, and getting it wrong is how people end up with access they shouldn't
 * have. Pick a preset, then adjust if needed.
 */
const USER_PRESETS = [
  {
    name: "Field technician",
    note: "Surveys and visits on site. Can see clients but not change them.",
    role: "tech",
    perms: ["surveys.view", "surveys.create", "surveys.edit", "clients.view"],
  },
  {
    name: "Office coordinator",
    note: "Runs clients, contracts, visits and reminders. No settings or users.",
    role: "staff",
    perms: ["dashboard.view", "clients.view", "clients.edit", "contracts.view", "contracts.edit",
            "reminders.send", "surveys.view", "surveys.edit"],
  },
  {
    name: "Software team — ZATCA only",
    note: "Sees the ZATCA page and nothing else. Can read submissions, view the onboarding code, and update progress.",
    role: "staff",
    perms: ["zatca.view", "zatca.secrets", "zatca.progress"],
  },
  {
    name: "Software team — read only",
    note: "Sees ZATCA submissions only. Cannot view codes or change anything.",
    role: "staff",
    perms: ["zatca.view"],
  },
];

/** Groups the flat permission list by the part of the system it covers. */
function groupPermissions(all) {
  const groups = {
    "Dashboard": ["dashboard."],
    "Clients": ["clients."],
    "AMC contracts and visits": ["contracts.", "reminders."],
    "Site surveys": ["surveys."],
    "ZATCA": ["zatca."],
    "Administration": ["settings."],
  };
  return Object.entries(groups)
    .map(([name, prefixes]) => [name, all.filter((p) => prefixes.some((x) => p.key.startsWith(x)))])
    .filter(([, perms]) => perms.length);
}

function UsersTab({ me }) {
  const [rows, setRows] = useState(null);
  const [edit, setEdit] = useState(null);
  const all = me.all_permissions || [];

  const load = useCallback(() => api("/users").then(setRows), []);
  useEffect(() => { load(); }, [load]);

  async function save(u) {
    try {
      if (u.id) await api("/users/" + u.id, { method: "PUT", body: u });
      else await api("/users", { method: "POST", body: u });
      setEdit(null); load();
    } catch (e) { alert(e.message); }
  }
  async function remove(u) {
    if (!confirm(`Delete user "${u.name}"? They will lose access immediately.`)) return;
    try { await api("/users/" + u.id, { method: "DELETE" }); load(); }
    catch (e) { alert(e.message); }
  }

  return (
    <div className="space-y-3">
      <div className="flex justify-between items-center">
        <p className="text-sm text-ink-faint">Admins always have every permission.</p>
        <Btn onClick={() => setEdit({ name: "", username: "", password: "", role: "staff", permissions: [], phone: "", email: "" })}>
          {Icons.plus} User
        </Btn>
      </div>

      {rows === null ? <p className="text-ink-faint">Loading…</p> : rows.map((u) => (
        <div key={u.id} className={card + " p-4"}>
          <div className="flex justify-between gap-2">
            <div className="min-w-0">
              <p className="font-medium text-ink">{u.name} {!u.active && <span className="text-xs text-rose-600">(disabled)</span>}</p>
              <p className="text-sm text-ink-faint">{u.username} · {u.role}</p>
              <p className="text-xs text-ink-faint">{[u.phone, u.email].filter(Boolean).join(" · ") || "no contact details"}</p>
              <p className="text-xs text-ink-faint mt-1">
                {u.role === "admin" ? "Full access — every screen" : describeAccess(u.permissions)}
              </p>
            </div>
            <div className="flex gap-1 shrink-0">
              <IconBtn icon={Icons.pencil} title="Edit" onClick={() => setEdit({ ...u, password: "" })} />
              <IconBtn icon={Icons.trash} title="Delete" tone="text-rose-400 hover:text-rose-600"
                onClick={() => remove(u)} />
            </div>
          </div>
        </div>
      ))}

      {edit && (
        <Modal title={edit.id ? "Edit user" : "New user"} onClose={() => setEdit(null)} wide>
          <div className="space-y-3">
            <div className="grid sm:grid-cols-2 gap-3">
              <Field label="Full name"><input className={input} value={edit.name}
                onChange={(e) => setEdit({ ...edit, name: e.target.value })} /></Field>
              <Field label="Username"><input className={input} value={edit.username} autoCapitalize="none"
                onChange={(e) => setEdit({ ...edit, username: e.target.value })} /></Field>
            </div>
            <div className="grid sm:grid-cols-2 gap-3">
              <Field label="Mobile / WhatsApp" hint="Used in survey assignment messages">
                <input className={input} value={edit.phone || ""}
                  onChange={(e) => setEdit({ ...edit, phone: e.target.value })} /></Field>
              <Field label="Email">
                <input className={input} value={edit.email || ""}
                  onChange={(e) => setEdit({ ...edit, email: e.target.value })} /></Field>
            </div>
            <Field label={edit.id ? "New password (leave blank to keep current)" : "Password"}>
              <input className={input} type="text" value={edit.password || ""}
                onChange={(e) => setEdit({ ...edit, password: e.target.value })} />
            </Field>
            <Field label="Start from a preset" hint="Sets the permissions below — adjust after if you like">
              <div className="grid sm:grid-cols-2 gap-2">
                {USER_PRESETS.map((p) => {
                  const active = edit.role !== "admin" &&
                    p.perms.length === (edit.permissions || []).length &&
                    p.perms.every((x) => (edit.permissions || []).includes(x));
                  return (
                    <button key={p.name}
                      onClick={() => setEdit({ ...edit, role: p.role, permissions: [...p.perms] })}
                      className={"rounded-lg border p-3 text-left " +
                        (active ? "border-brand-500 bg-brand-50" : "border-gray-300 bg-white hover:bg-gray-50")}>
                      <p className="text-sm font-medium text-ink">{p.name}</p>
                      <p className="text-xs text-ink-faint mt-0.5">{p.note}</p>
                    </button>
                  );
                })}
              </div>
            </Field>

            <Field label="Role" hint="Admin always has every permission, whatever is ticked below">
              <Pills options={["admin", "staff", "tech"]} value={edit.role}
                onChange={(v) => setEdit({ ...edit, role: v })} />
            </Field>
            {edit.id && (
              <label className="flex items-center gap-3 rounded-lg border border-gray-200 p-3">
                <input type="checkbox" className="w-5 h-5 accent-brand-500" checked={!!edit.active}
                  onChange={(e) => setEdit({ ...edit, active: e.target.checked ? 1 : 0 })} />
                <span className="text-sm text-ink-soft">Account is active</span>
              </label>
            )}
            {edit.role === "admin" ? (
              <p className="text-sm text-ink-soft rounded-lg bg-brand-50 border border-brand-200 p-3">
                Admins always have every permission, including settings, users and backup.
              </p>
            ) : (
              <>
                <Field label={`Permissions — ${describeAccess(edit.permissions || [])}`}>
                  <div className="space-y-3">
                    {groupPermissions(all).map(([group, perms]) => {
                      const keys = perms.map((p) => p.key);
                      const allOn = keys.every((k) => (edit.permissions || []).includes(k));
                      return (
                        <div key={group} className="rounded-xl border border-gray-200 p-3">
                          <div className="flex items-center justify-between mb-2">
                            <p className="text-sm font-semibold text-ink">{group}</p>
                            <button className="text-xs text-brand-600"
                              onClick={() => setEdit({
                                ...edit,
                                permissions: allOn
                                  ? (edit.permissions || []).filter((k) => !keys.includes(k))
                                  : [...new Set([...(edit.permissions || []), ...keys])],
                              })}>
                              {allOn ? "Clear all" : "Select all"}
                            </button>
                          </div>
                          <div className="grid sm:grid-cols-2 gap-1.5">
                            {perms.map((p) => (
                              <label key={p.key} className="flex items-center gap-2 rounded-lg border border-gray-200 px-3 py-2">
                                <input type="checkbox" className="w-4 h-4 accent-brand-500"
                                  checked={(edit.permissions || []).includes(p.key)}
                                  onChange={(e) => setEdit({
                                    ...edit,
                                    permissions: e.target.checked
                                      ? [...(edit.permissions || []), p.key]
                                      : (edit.permissions || []).filter((x) => x !== p.key),
                                  })} />
                                <span className="text-sm text-ink-soft">{p.label}</span>
                              </label>
                            ))}
                          </div>
                        </div>
                      );
                    })}
                  </div>
                </Field>
              </>
            )}
            <div className="flex gap-2 pt-2">
              <Btn kind="ghost" onClick={() => setEdit(null)}>Cancel</Btn>
              <Btn onClick={() => save(edit)} className="flex-1">Save user</Btn>
            </div>
          </div>
        </Modal>
      )}
    </div>
  );
}

/** Plain-language summary of which screens a permission set opens. */
function describeAccess(perms = []) {
  const screens = [
    ["Dashboard", "dashboard.view"], ["Clients", "clients.view"], ["AMC", "contracts.view"],
    ["Visits", "contracts.view"], ["Surveys", "surveys.view"], ["ZATCA", "zatca.view"],
    ["Settings", "settings.manage"],
  ].filter(([, p]) => perms.includes(p)).map(([n]) => n);
  const unique = [...new Set(screens)];
  if (!unique.length) return "No screens — this account can sign in but see nothing";
  return `Sees: ${unique.join(", ")}`;
}

function SettingsForm({ section }) {
  const [s, setS] = useState(null);
  const [saved, setSaved] = useState("");
  const [busy, setBusy] = useState(false);
  useEffect(() => { api("/settings").then(setS); }, []);
  if (!s) return <p className="text-ink-faint">Loading…</p>;
  const set = (k) => (e) => setS({ ...s, [k]: e.target.value });

  async function save() {
    setBusy(true);
    try { await api("/settings", { method: "PUT", body: s }); setSaved("Saved"); setTimeout(() => setSaved(""), 2000); }
    catch (e) { alert(e.message); } finally { setBusy(false); }
  }

  async function testDigest() {
    try { const r = await api("/reminders/digest", { method: "POST" }); alert("Summary sent: " + r.result); }
    catch (e) { alert(e.message); }
  }
  async function runSweep() {
    try { const r = await api("/reminders/run", { method: "POST" }); alert(`${r.sent} customer alert(s) sent.`); }
    catch (e) { alert(e.message); }
  }

  return (
    <div className="space-y-3">
      {section === "company" && (
        <>
          <Field label="Company name"><input className={input} value={s.company_name} onChange={set("company_name")} /></Field>
          <Field label="Public web address" hint="Needed for shareable PDF and feedback links">
            <input className={input} value={s.public_base_url} onChange={set("public_base_url")}
              placeholder="https://field.reliable-itsolution.com" /></Field>
          <Field label="Google review link" hint="From Google Business Profile → Ask for reviews">
            <input className={input} value={s.gmb_review_url} onChange={set("gmb_review_url")}
              placeholder="https://g.page/r/..." /></Field>
          <Field label="Businesses" hint="Comma separated, for tagging records">
            <input className={input} value={s.companies} onChange={set("companies")} /></Field>
          <Field label="Tagline"><input className={input} value={s.company_tagline} onChange={set("company_tagline")} /></Field>
          <Field label="Phone"><input className={input} value={s.company_phone} onChange={set("company_phone")} /></Field>
          <Field label="Address"><textarea rows="2" className={input} value={s.company_address} onChange={set("company_address")} /></Field>
        </>
      )}

      {section === "notify" && (
        <>
          <div className={card + " p-4 space-y-3"}>
            <h3 className="font-semibold text-ink">Where your own alerts go</h3>
            <p className="text-sm text-ink-faint">
              Once a day the system pushes a summary of everything expiring to these,
              so nobody has to open the software to find out.
            </p>
            <Field label="Company email"><input className={input} value={s.company_email} onChange={set("company_email")} /></Field>
            <Field label="Company WhatsApp" hint="With country code">
              <input className={input} value={s.company_whatsapp} onChange={set("company_whatsapp")} /></Field>
            <div className="grid sm:grid-cols-2 gap-3">
              <Field label="Daily summary at (hour)"><input type="number" min="0" max="23" className={input}
                value={s.digest_hour} onChange={set("digest_hour")} /></Field>
              <Field label="Send summary?">
                <Pills options={["1", "0"]} value={s.notify_internal}
                  onChange={(v) => setS({ ...s, notify_internal: v })} />
              </Field>
            </div>
            <div className="flex gap-2">
              <Btn kind="soft" onClick={testDigest}>Send summary now</Btn>
              <Btn kind="soft" onClick={runSweep}>Run reminder sweep</Btn>
            </div>
          </div>

          <div className={card + " p-4 space-y-3"}>
            <h3 className="font-semibold text-ink">Customer reminders</h3>
            <div className="grid sm:grid-cols-2 gap-3">
              <Field label="Days before expiry" hint="Comma separated">
                <input className={input} value={s.expiry_days} onChange={set("expiry_days")} /></Field>
              <Field label="Days before a visit">
                <input className={input} value={s.visit_days} onChange={set("visit_days")} /></Field>
            </div>
            <Field label="Notify customers automatically?">
              <Pills options={["1", "0"]} value={s.notify_customer}
                onChange={(v) => setS({ ...s, notify_customer: v })} />
            </Field>
            <Field label="Add Arabic to customer messages?" hint="Appends an Arabic version below the English">
              <Pills options={["1", "0"]} value={s.bilingual_messages}
                onChange={(v) => setS({ ...s, bilingual_messages: v })} />
            </Field>
          </div>

          <div className={card + " p-4 space-y-3"}>
            <h3 className="font-semibold text-ink">Email (SMTP)</h3>
            <p className="text-sm text-ink-faint">Leave blank and reminders print to the server console instead of sending.</p>
            <div className="grid sm:grid-cols-2 gap-3">
              <Field label="Host"><input className={input} value={s.smtp_host} onChange={set("smtp_host")} placeholder="smtp.hostinger.com" /></Field>
              <Field label="Port"><input className={input} value={s.smtp_port} onChange={set("smtp_port")} /></Field>
            </div>
            <Field label="Username"><input className={input} value={s.smtp_user} onChange={set("smtp_user")} /></Field>
            <Field label="Password"><input className={input} type="password" value={s.smtp_pass} onChange={set("smtp_pass")} /></Field>
            <Field label="From address"><input className={input} value={s.smtp_from} onChange={set("smtp_from")} /></Field>
          </div>

          <div className={card + " p-4 space-y-3"}>
            <h3 className="font-semibold text-ink">WhatsApp (Meta Cloud API)</h3>
            <p className="text-sm text-ink-faint">
              Optional. Without this you can still send WhatsApp with one tap from each contract.
              Automatic sending needs a Meta Business account and approved message templates.
            </p>
            <Field label="Phone number ID"><input className={input} value={s.wa_phone_number_id} onChange={set("wa_phone_number_id")} /></Field>
            <Field label="Access token"><input className={input} type="password" value={s.wa_token} onChange={set("wa_token")} /></Field>
            <div className="grid sm:grid-cols-2 gap-3">
              <Field label="Expiry template"><input className={input} value={s.wa_template_expiry} onChange={set("wa_template_expiry")} /></Field>
              <Field label="Visit template"><input className={input} value={s.wa_template_visit} onChange={set("wa_template_visit")} /></Field>
            </div>
          </div>
        </>
      )}

      <div className="flex items-center gap-3 pt-1">
        <Btn onClick={save} disabled={busy}>Save settings</Btn>
        {saved && <span className="text-sm text-brand-600">{saved}</span>}
      </div>
    </div>
  );
}

/** Reminders that went out, and feedback that came back. */
function ActivityTab() {
  const [reminders, setReminders] = useState([]);
  const [feedback, setFeedback] = useState([]);
  useEffect(() => {
    api("/reminders").then(setReminders).catch(() => {});
    api("/feedback").then(setFeedback).catch(() => {});
  }, []);

  const stars = (n) => (n ? "★".repeat(n) + "☆".repeat(5 - n) : "not answered yet");

  return (
    <div className="space-y-3">
      <div className={card + " p-4"}>
        <h3 className="font-semibold text-ink mb-1">Customer feedback</h3>
        <p className="text-sm text-ink-faint mb-3">
          4 or 5 stars offers the customer your Google review page. 1 to 3 stars comes here privately instead.
        </p>
        {feedback.length === 0 ? <p className="text-sm text-ink-faint">Nothing sent yet.</p> : feedback.map((f) => (
          <div key={f.id} className="border-t border-gray-100 py-2">
            <div className="flex justify-between gap-2">
              <p className="text-sm font-medium text-ink truncate">{f.client_name}</p>
              <span className={"text-sm whitespace-nowrap " + (f.rating >= 4 ? "text-emerald-600" : f.rating ? "text-amber-600" : "text-ink-faint")}>
                {stars(f.rating)}
              </span>
            </div>
            <p className="text-xs text-ink-faint">
              {f.channel} · sent {(f.sent_at || "").slice(0, 10)}
              {f.went_to_gmb ? " · offered Google review" : ""}
            </p>
            {f.comment && <p className="text-sm text-ink-soft mt-1">{f.comment}</p>}
          </div>
        ))}
      </div>

      <div className={card + " p-4"}>
        <h3 className="font-semibold text-ink mb-3">Reminders sent</h3>
        {reminders.length === 0 ? <p className="text-sm text-ink-faint">None yet.</p> : reminders.map((r) => (
          <div key={r.id} className="border-t border-gray-100 py-2 flex justify-between gap-2">
            <div className="min-w-0">
              <p className="text-sm text-ink truncate">{r.client_name}</p>
              <p className="text-xs text-ink-faint">
                {r.kind} · {r.days_out} days · {r.channel} · {r.sent_to || "no address"}
              </p>
            </div>
            <span className={"text-xs whitespace-nowrap " + (r.ok ? "text-emerald-600" : "text-rose-600")}>
              {r.ok ? "sent" : "failed"}
            </span>
          </div>
        ))}
      </div>
    </div>
  );
}

/** Who did what. Nothing here can be edited or removed from the app. */
function AuditTab() {
  const [rows, setRows] = useState([]);
  const [action, setAction] = useState("");
  const [q, setQ] = useState("");

  const load = useCallback(() => {
    api(`/audit?action=${encodeURIComponent(action)}&q=${encodeURIComponent(q)}`)
      .then(setRows).catch(() => {});
  }, [action, q]);
  useEffect(() => { load(); }, [load]);

  const tone = { delete: "text-rose-600", clear: "text-rose-600", create: "text-emerald-600",
                 update: "text-brand-600", login: "text-ink-faint", sign: "text-emerald-600",
                 share: "text-brand-600", restore: "text-amber-600" };

  return (
    <div className="space-y-3">
      <p className="text-sm text-ink-faint">
        Every change, with the person who made it. Useful when a record goes missing.
      </p>
      <input className={input} placeholder="Search by record or username" value={q}
        onChange={(e) => setQ(e.target.value)} />
      <Pills options={["", "create", "update", "delete", "login", "clear"]}
        value={action} onChange={setAction} />
      <div className={card + " p-4"}>
        {rows.length === 0 ? <p className="text-sm text-ink-faint">Nothing recorded yet.</p> : rows.map((r) => (
          <div key={r.id} className="border-t border-gray-100 py-2 first:border-0 first:pt-0">
            <div className="flex justify-between gap-2">
              <p className="text-sm text-ink truncate">
                <span className={"font-medium " + (tone[r.action] || "")}>{r.action}</span>
                {" "}{r.entity}{r.label ? ` — ${r.label}` : ""}
              </p>
              <span className="text-xs text-ink-faint whitespace-nowrap">
                {(r.created_at || "").slice(5, 16).replace("T", " ")}
              </span>
            </div>
            <p className="text-xs text-ink-faint">
              {r.username}{r.detail ? ` · ${r.detail}` : ""}{r.ip ? ` · ${r.ip}` : ""}
            </p>
          </div>
        ))}
      </div>
    </div>
  );
}

function DataTab() {
  const [busy, setBusy] = useState("");
  const [backups, setBackups] = useState([]);

  const loadBackups = useCallback(() => api("/data/backups").then(setBackups).catch(() => {}), []);
  useEffect(() => { loadBackups(); }, [loadBackups]);

  // Every destructive action asks for the signed-in user's own password,
  // so a phone left unlocked on a desk can't wipe the system in two taps.
  function askPassword(action) {
    const p = prompt(`Confirm your password to ${action}:`);
    return p && p.trim() ? p : null;
  }

  async function backup() {
    const res = await fetch("/api/data/backup", { headers: { Authorization: "Bearer " + tok.get() } });
    if (!res.ok) return alert("Backup failed");
    const blob = await res.blob();
    const a = document.createElement("a");
    a.href = URL.createObjectURL(blob);
    a.download = `rits-backup-${new Date().toISOString().slice(0, 10)}.json`;
    a.click();
    URL.revokeObjectURL(a.href);
  }

  async function restore(e) {
    const file = e.target.files?.[0];
    if (!file) return;
    if (!confirm("Restoring replaces everything currently in the system.\n\nA backup of the current data is saved first. Continue?")) {
      e.target.value = ""; return;
    }
    const password = askPassword("restore from this file");
    if (!password) { e.target.value = ""; return; }
    setBusy("restore");
    try {
      const dump = JSON.parse(await file.text());
      const r = await api("/data/restore", { method: "POST", body: { dump, password } });
      alert(r.message); tok.clear(); location.reload();
    } catch (err) { alert(err.message); } finally { setBusy(""); e.target.value = ""; }
  }

  async function rollback(name) {
    if (!confirm(`Roll everything back to:\n\n${name}\n\nCurrent data is backed up first. Continue?`)) return;
    const password = askPassword("roll back");
    if (!password) return;
    try {
      const r = await api("/data/restore-backup", { method: "POST", body: { name, password } });
      alert(r.message); tok.clear(); location.reload();
    } catch (e) { alert(e.message); }
  }

  function downloadBackup(name) {
    fetch("/api/data/backups/" + encodeURIComponent(name), {
      headers: { Authorization: "Bearer " + tok.get() },
    }).then((r) => r.blob()).then((blob) => {
      const a = document.createElement("a");
      a.href = URL.createObjectURL(blob);
      a.download = name;
      a.click();
      URL.revokeObjectURL(a.href);
    });
  }

  async function clearTx() {
    if (!confirm("Delete all contracts, visits, surveys and reminder history?\n\nClients and users are kept, and a backup is saved first.")) return;
    const password = askPassword("clear all transactions");
    if (!password) return;
    try {
      const r = await api("/data/clear-transactions", { method: "POST", body: { password } });
      alert(r.message); loadBackups();
    } catch (e) { alert(e.message); }
  }

  async function clearAll() {
    const typed = prompt("This erases clients, contracts, surveys, photos and all other users.\nA backup is saved first.\n\nType DELETE EVERYTHING to confirm:");
    if (typed !== "DELETE EVERYTHING") return;
    const password = askPassword("clear the entire database");
    if (!password) return;
    try {
      const r = await api("/data/clear-all", { method: "POST", body: { confirm: typed, password } });
      alert(r.message); location.reload();
    } catch (e) { alert(e.message); }
  }

  return (
    <div className="space-y-3">
      <div className={card + " p-4 space-y-2"}>
        <h3 className="font-semibold text-ink">Backup</h3>
        <p className="text-sm text-ink-faint">
          Downloads one JSON file with every client, contract, survey, user and setting.
          Site photos are files on the server — copy the <code className="text-xs">uploads</code> folder as well.
        </p>
        <Btn kind="soft" onClick={backup}>{Icons.down} Download backup</Btn>
      </div>

      <div className={card + " p-4 space-y-2"}>
        <h3 className="font-semibold text-ink">Spreadsheet exports</h3>
        <p className="text-sm text-ink-faint">
          Opens directly in Excel, including Arabic text. Use these for reporting or to hand figures to your ERP.
        </p>
        <div className="flex flex-wrap gap-2">
          <a href={fileUrl("/api/export/contracts")}
             className="min-h-10 rounded-lg px-3.5 text-sm font-medium inline-flex items-center gap-2 bg-brand-50 text-brand-700">
            {Icons.down} Contracts
          </a>
          <a href={fileUrl("/api/export/surveys")}
             className="min-h-10 rounded-lg px-3.5 text-sm font-medium inline-flex items-center gap-2 bg-brand-50 text-brand-700">
            {Icons.down} Surveys
          </a>
        </div>
      </div>

      <div className={card + " p-4 space-y-2"}>
        <h3 className="font-semibold text-ink">Restore from a file</h3>
        <p className="text-sm text-ink-faint">Replaces everything with a backup file. Your password is required.</p>
        <label className="inline-flex">
          <span className="min-h-10 rounded-lg px-3.5 text-sm font-medium inline-flex items-center gap-2 bg-brand-50 text-brand-700 cursor-pointer">
            {Icons.up} {busy === "restore" ? "Restoring…" : "Choose backup file"}
          </span>
          <input type="file" accept="application/json" className="hidden" onChange={restore} />
        </label>
      </div>

      <div className={card + " p-4 space-y-3"}>
        <h3 className="font-semibold text-ink">Automatic safety backups</h3>
        <p className="text-sm text-ink-faint">
          Taken automatically before every clear or restore, and kept on the server in the
          <code className="text-xs"> backups</code> folder. The 30 most recent are kept.
          If something was cleared by mistake, roll back to the snapshot from just before it.
        </p>
        {backups.length === 0 ? (
          <p className="text-sm text-ink-faint">None yet — one is created the first time you clear or restore.</p>
        ) : (
          <div className="space-y-2">
            {backups.map((b) => (
              <div key={b.name} className="flex items-center justify-between gap-2 border-t border-gray-100 pt-2 first:border-0 first:pt-0">
                <div className="min-w-0">
                  <p className="text-sm text-ink truncate">{b.name.replace(/^auto-/, "").replace(/\.json$/, "")}</p>
                  <p className="text-xs text-ink-faint">
                    {new Date(b.taken_at).toLocaleString("en-GB")} · {Math.round(b.size / 1024)} KB
                  </p>
                </div>
                <div className="flex gap-1 shrink-0">
                  <Btn kind="ghost" onClick={() => downloadBackup(b.name)}>Download</Btn>
                  <Btn kind="soft" onClick={() => rollback(b.name)}>Roll back</Btn>
                </div>
              </div>
            ))}
          </div>
        )}
      </div>

      <div className={card + " p-4 space-y-2 border-rose-200"}>
        <h3 className="font-semibold text-rose-700">Danger zone</h3>
        <p className="text-sm text-ink-faint">
          Both ask for your password and save a backup first, so a mistake can always be undone.
        </p>
        <div className="flex flex-wrap gap-2">
          <Btn kind="danger" onClick={clearTx}>Clear all transactions</Btn>
          <Btn kind="danger" onClick={clearAll}>Clear entire database</Btn>
        </div>
        <p className="text-xs text-ink-faint pt-1">
          Locked out completely? Stop the server and run <code>npm run reset-admin</code> in the app
          folder — it resets admin back to admin123.
        </p>
      </div>
    </div>
  );
}

/* ================================ app ================================ */

function App() {
  const [me, setMe] = useState(null);
  const [checking, setChecking] = useState(true);
  const [tab, setTab] = useState("dashboard");
  const [formOpen, setFormOpen] = useState(false);
  const [mismatch, setMismatch] = useState(null);
  const [waJob, setWaJob] = useState(null);

  // Every openWhatsApp() call anywhere in the app routes through this one dialog.
  useEffect(() => {
    openWhatsAppDialog = (job) => setWaJob(job);
    return () => { openWhatsAppDialog = null; };
  }, []);
  const onFormOpen = useCallback((v) => setFormOpen(v), []);

  // A folder where only some files were replaced is the single most confusing
  // failure to diagnose, so check it explicitly and say so.
  useEffect(() => {
    fetch("/api/health").then((r) => r.json()).then((h) => {
      if (h.version !== APP_VERSION) setMismatch(h.version || "older than 3.0");
    }).catch(() => {});
  }, []);

  const loadMe = useCallback(async () => {
    try {
      const u = await api("/me");
      setMe(u);
      const first = ["dashboard", "clients", "contracts", "surveys", "zatca", "settings"]
        .find((t) => u.permissions.includes(t === "settings" ? "settings.manage" : t + ".view"));
      // "visits" rides on contracts.view, so it needs no entry of its own above.
      setTab(first || "surveys");
    } catch { /* not signed in */ }
    setChecking(false);
  }, []);

  useEffect(() => { tok.get() ? loadMe() : setChecking(false); }, [loadMe]);

  if (checking) return <div className="min-h-screen flex items-center justify-center text-ink-faint">Loading…</div>;
  if (!me) return <Login onIn={() => { setChecking(true); loadMe(); }} />;

  const can = (p) => me.permissions.includes(p);
  const nav = [
    ["dashboard", "Home", Icons.dash, can("dashboard.view")],
    ["clients", "Clients", Icons.users, can("clients.view")],
    ["contracts", "AMC", Icons.doc, can("contracts.view")],
    ["visits", "Visits", Icons.cal, can("contracts.view")],
    ["surveys", "Surveys", Icons.clip, can("surveys.view")],
    ["zatca", "ZATCA", Icons.shield, can("zatca.view")],
    ["settings", "Settings", Icons.gear, can("settings.manage")],
  ].filter((n) => n[3]);

  return (
    <div className="min-h-screen pb-20 lg:pb-6">
      <header className="sticky top-0 z-30 bg-white border-b border-gray-200">
        <div className="mx-auto max-w-3xl md:max-w-5xl xl:max-w-7xl px-4 py-2.5 flex items-center gap-4">
          <div className="flex items-center gap-2.5 shrink-0">
            <img src="/sitecare-icon-teal.svg" alt="" className="h-9 w-9 rounded-lg" />
            <div className="leading-none hidden sm:block">
              <p className="text-lg font-semibold tracking-tight">
                <span className="text-ink">Site</span><span className="text-brand-500">Care</span>
              </p>
              <p className="text-xs text-ink-faint mt-0.5">by Reliable iT</p>
            </div>
          </div>

          {/* On a laptop the tabs belong up here; a phone keeps them within thumb reach. */}
          {!formOpen && (
            <nav className="hidden lg:flex items-center gap-1 flex-1">
              {nav.map(([id, label, icon]) => (
                <button key={id} onClick={() => setTab(id)}
                  className={"min-h-10 rounded-lg px-3 text-sm font-medium inline-flex items-center gap-2 " +
                    (tab === id ? "bg-brand-50 text-brand-700" : "text-ink-soft hover:bg-gray-50")}>
                  {icon}{label}
                </button>
              ))}
            </nav>
          )}

          <div className="ml-auto text-right leading-tight shrink-0">
            <p className="text-sm font-medium text-ink">{me.name}</p>
            <button onClick={async () => { await api("/logout", { method: "POST" }); tok.clear(); location.reload(); }}
              className="text-xs text-ink-faint hover:text-brand-600">Sign out</button>
          </div>
        </div>
      </header>

      {mismatch && (
        <div className="bg-rose-50 border-b border-rose-200 px-4 py-3">
          <p className="mx-auto max-w-3xl md:max-w-5xl xl:max-w-7xl px-4 text-sm text-rose-800">
            These files don't match: the screen is version {APP_VERSION} but the server is
            version {mismatch}. Replace the whole folder with the latest download, keep your
            data.db, restart with npm start, then refresh with Ctrl+Shift+R.
          </p>
        </div>
      )}

      <main className="mx-auto max-w-3xl md:max-w-5xl xl:max-w-7xl px-4 py-5">
        {tab === "dashboard" && can("dashboard.view") && <Dashboard me={me} go={setTab} />}
        {tab === "clients" && can("clients.view") && <Clients me={me} />}
        {tab === "contracts" && can("contracts.view") && <Contracts me={me} />}
        {tab === "visits" && can("contracts.view") && <Visits me={me} />}
        {tab === "surveys" && can("surveys.view") && <Surveys me={me} onFormOpen={onFormOpen} />}
        {tab === "zatca" && can("zatca.view") && <Zatca me={me} />}
        {tab === "settings" && can("settings.manage") && <Settings me={me} reloadMe={loadMe} />}
      </main>

      {waJob && <WhatsAppDialog job={waJob} onClose={() => setWaJob(null)} />}

      <footer className="mx-auto max-w-3xl md:max-w-5xl xl:max-w-7xl px-4 pb-4 text-center">
        <p className="text-xs text-ink-faint">
          SiteCare — site survey &amp; AMC management · by Reliable iT, Al Jubail
        </p>
      </footer>

      {!formOpen && (
        <nav className="fixed bottom-0 inset-x-0 bg-white border-t border-gray-200 z-30 lg:hidden">
          <div className="mx-auto max-w-3xl md:max-w-5xl xl:max-w-7xl flex">
            {nav.map(([id, label, icon]) => (
              <button key={id} onClick={() => setTab(id)}
                className={"flex-1 py-2.5 flex flex-col items-center gap-0.5 text-xs font-medium " +
                  (tab === id ? "text-brand-600" : "text-ink-faint")}>
                {icon}{label}
              </button>
            ))}
          </div>
        </nav>
      )}
    </div>
  );
}

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