/* global React, ReactDOM */
const { useState, useEffect, useRef } = React;

/* ---- inline SVG icons ---- */
const Svg = (paths, extra = {}) => ({ size = 16, style, ...rest }) =>
  React.createElement(
    "svg",
    {
      width: size, height: size, viewBox: "0 0 24 24", fill: "none",
      stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round",
      strokeLinejoin: "round", style, ...extra, ...rest,
    },
    paths.map((d, i) => React.createElement("path", { key: i, d })),
  );
const ArrowRight = Svg(["M5 12h14", "m12 5 7 7-7 7"]);
const Plus = Svg(["M5 12h14", "M12 5v14"]);
const Trash2 = Svg(["M3 6h18", "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2", "M10 11v6", "M14 11v6"]);
const Check = Svg(["M20 6 9 17l-5-5"]);
const Cog = Svg(["M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6z", "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 .33-1.82 1.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 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 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 .33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"]);
const Layers = Svg(["M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83Z", "M2 12a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 12", "M2 17a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 17"]);
const FileText = Svg(["M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z", "M14 2v4a2 2 0 0 0 2 2h4", "M10 9H8", "M16 13H8", "M16 17H8"]);
const Send = Svg(["M14.536 21.686a.5.5 0 0 0 .937-.024l4.5-16a.5.5 0 0 0-.672-.627L3.14 10.3a.5.5 0 0 0 .02.934l6.36 2.12 2.12 6.36z"]);
const Mic = Svg(["M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z", "M19 10v2a7 7 0 0 1-14 0v-2", "M12 19v3"]);
const Users = Svg([
  "M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2",
  "M9 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8z",
  "M22 21v-2a4 4 0 0 0-3-3.87",
  "M16 3.13a4 4 0 0 1 0 7.75",
]);

const SCOPE_API = "/api/scope";
const SCOPE_CHAT_API = "/api/scope-chat";
const STORAGE_KEY = "ayg_scope_builder_v3";

/* Ask Yr Grandpa Asset Kit tokens. Do not invent a second palette. */
const C = {
  paper: "#F5F3F0",
  surface: "#ffffff",
  ink: "#111110",
  grey: "#5F5C58",
  line: "#E5E2DE",
  lineSoft: "#d9d4cd",
  accent: "#E05E1F",
  accentDeep: "#C14E15",
  accentSoft: "#fbeae2",
  accentTint: "#f0c6ae",
  success: "#2f6b3a",
};
const FONT = "'Inter Tight', Inter, system-ui, sans-serif";
const VOICE_LANGS = ["en-AU", "en-GB", "en-US"];
const VOICE_UNSUPPORTED = "Voice input not supported in this browser — type instead.";

function getSpeechRecognition() {
  if (typeof window === "undefined") return null;
  return window.SpeechRecognition || window.webkitSpeechRecognition || null;
}
function joinTranscript(committed, interim) {
  const a = String(committed || "").replace(/\s+$/, "");
  const b = String(interim || "").replace(/^\s+/, "");
  if (!b) return a;
  return a ? a + " " + b : b;
}

const INDUSTRIES = [
  "Aged Care", "Agriculture / Primary Industries", "Allied Health", "Automotive",
  "Building & Construction", "Childcare / Early Learning", "Community Services / NGO",
  "Ecommerce / Online Retail", "Education & Training", "Financial Services",
  "Fitness & Wellness", "Healthcare / Medical", "Hospitality & Tourism",
  "Manufacturing", "NDIS / Disability Support", "Professional Services",
  "Real Estate / Property", "Recruitment / Labour Hire", "Retail",
  "Sign, Print & Signage", "Trades & Services", "Transport & Logistics",
  "Other",
];
const COUNTRIES = ["Australia", "New Zealand", "Other"];
const AYG_PRODUCTS = [
  "New custom build (start from scratch)",
  "Titus CRM", "LIFE-AI", "Cater2Me", "Poto AI", "GetSignQuote", "AI Receptionist",
];
const TOOL_OPTIONS = [
  "ActiveCampaign", "Excel / Google Sheets", "GoHighLevel", "HubSpot", "Keap",
  "Mailchimp", "Microsoft Dynamics", "monday.com", "MYOB", "Outlook / Gmail",
  "Paper / manual", "Pipedrive", "QuickBooks", "Salesforce", "ShiftCare",
  "WhatsApp", "Xero", "Zendesk", "Zoho CRM", "Other",
];
const SALARY_PERIODS = ["annual", "monthly", "weekly", "hourly"];
const SPEND_PERIODS = ["monthly", "annual", "unknown"];

const GREETING = "G'day, I'm Ethan. We'll map the work together. After you confirm, you'll get a priced quote you can Approve or Reject. First up, what's the business name?";
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
const uid = () => Math.random().toString(36).slice(2, 9);
const isEmail = (v) => EMAIL_RE.test((v || "").trim());
const filled = (v) => Boolean(String(v || "").trim());

function emptyRole(id) {
  return { id: id || uid(), job_title: "", headcount: "", approx_salary: "", salary_period: "annual" };
}
function emptyPlatform(id) {
  return { id: id || uid(), name: "", approx_spend: "", spend_period: "monthly", users: "" };
}
function emptyBusiness() {
  return {
    name: "", first_name: "", last_name: "", contactName: "", job_title: "",
    email: "", phone: "", industry: "", country: "", product: "",
    teamSize: "", tools: [], toolUsage: {}, platforms: [],
    org_structure: [], org_structure_skipped: false, org_structure_complete: false, org_structure_note: "",
    platforms_skipped: false, platforms_complete: false, platforms_note: "",
    headache: "", skipped: {},
    filler_is_director: null,
    director: { first_name: "", last_name: "", email: "", phone: "" },
  };
}
function contactDisplayName(business) {
  const first = String((business && business.first_name) || "").trim();
  const last = String((business && business.last_name) || "").trim();
  const joined = [first, last].filter(Boolean).join(" ");
  return joined || String((business && (business.contactName || business.contact_name)) || "").trim();
}
function syncFillerName(next) {
  let first = String(next.first_name || "").trim();
  let last = String(next.last_name || "").trim();
  if (!first || !last) {
    const from = splitPersonName(next.contactName || next.contact_name);
    if (!first) first = from.first_name;
    if (!last) last = from.last_name;
  }
  next.first_name = first;
  next.last_name = last;
  next.contactName = [first, last].filter(Boolean).join(" ");
  return next;
}
function hydrateBusiness(raw) {
  const base = emptyBusiness();
  if (!raw || typeof raw !== "object") return base;
  const next = {
    ...base,
    ...raw,
    skipped: { ...base.skipped, ...(raw.skipped || {}) },
    director: { ...base.director, ...(raw.director || {}) },
    tools: Array.isArray(raw.tools) ? raw.tools.slice() : [],
    toolUsage: raw.toolUsage && typeof raw.toolUsage === "object" ? { ...raw.toolUsage } : {},
    org_structure: Array.isArray(raw.org_structure) ? raw.org_structure.map(normalizeRole) : [],
    platforms: Array.isArray(raw.platforms) && raw.platforms.length
      ? raw.platforms.map(normalizePlatform)
      : migrateTools(raw),
    org_structure_skipped: false,
    org_structure_complete: !!raw.org_structure_complete,
    org_structure_note: String(raw.org_structure_note || ""),
    platforms_skipped: !!raw.platforms_skipped,
    platforms_complete: !!raw.platforms_complete,
    platforms_note: String(raw.platforms_note || ""),
    filler_is_director: raw.filler_is_director === true ? true : raw.filler_is_director === false ? false : null,
    job_title: String(raw.job_title || raw.contact_job_title || raw.filler_job_title || "").trim(),
  };
  syncFillerName(next);
  if (next.filler_is_director === true && !next.director.first_name && !next.director.email) {
    next.director = directorFromFiller(next);
  }
  return next;
}
function normalizeRole(r) {
  const x = r && typeof r === "object" ? r : {};
  return {
    id: x.id || uid(),
    job_title: String(x.job_title || x.title || "").trim(),
    headcount: x.headcount == null || x.headcount === "" ? "" : x.headcount,
    approx_salary: x.approx_salary == null || x.approx_salary === "" ? "" : x.approx_salary,
    salary_period: x.salary_period || "annual",
    salary_skipped: !!x.salary_skipped,
  };
}
function normalizePlatform(p) {
  const x = p && typeof p === "object" ? p : {};
  return {
    id: x.id || uid(),
    name: String(x.name || x.tool || "").trim(),
    approx_spend: x.approx_spend == null || x.approx_spend === "" ? "" : x.approx_spend,
    spend_period: x.spend_period || "unknown",
    users: x.users == null || x.users === "" ? "" : x.users,
    spend_skipped: !!x.spend_skipped,
    users_skipped: !!x.users_skipped,
  };
}
function migrateTools(raw) {
  const tools = Array.isArray(raw && raw.tools) ? raw.tools : [];
  return tools.map((name) => normalizePlatform({ name }));
}
function roleComplete(r) {
  const n = normalizeRole(r);
  const count = Number(n.headcount);
  const salaryOk = n.approx_salary !== "" && n.approx_salary != null;
  return !!(n.job_title && Number.isFinite(count) && count >= 1 && salaryOk);
}
function hasCompleteOrg(b) {
  return (b.org_structure || []).some(roleComplete);
}
function platformComplete(p) {
  const n = normalizePlatform(p);
  return !!(n.name && (n.approx_spend !== "" || n.spend_skipped) && (n.users !== "" || n.users_skipped));
}
function orgSettled(b) {
  return !!(b.org_structure_complete && hasCompleteOrg(b));
}
function platformsSettled(b) {
  return !!(b.platforms_skipped || b.platforms_complete);
}
function parseRoughNumber(text, { money = false } = {}) {
  const raw = String(text || "").trim();
  if (!raw) return null;
  const k = raw.replace(/,/g, "").match(/(\d+(?:\.\d+)?)\s*k\b/i);
  if (k) return Math.round(Number(k[1]) * 1000);
  const m = raw.replace(/,/g, "").match(/(\d+(?:\.\d+)?)/);
  if (!m) return null;
  let n = Number(m[1]);
  if (money && n >= 15 && n < 1000 && !/month|hour|week/i.test(raw)) n *= 1000;
  return Number.isFinite(n) ? n : null;
}
function detectPeriod(text, fallback) {
  const raw = String(text || "");
  if (/hour|hourly/i.test(raw)) return "hourly";
  if (/week|weekly/i.test(raw)) return "weekly";
  if (/month|monthly/i.test(raw)) return "monthly";
  if (/year|annual/i.test(raw)) return "annual";
  return fallback;
}
function formatAud(amount) {
  const n = Number(amount);
  if (!Number.isFinite(n)) return "";
  return "AUD " + Math.round(n).toLocaleString("en-AU");
}
function moneyFmt(n) {
  const v = Math.round((Number(n) || 0) * 100) / 100;
  const [int, dec] = Math.abs(v).toFixed(2).split(".");
  const withCommas = int.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
  return (v < 0 ? "-$" : "$") + withCommas + "." + dec;
}
function fmtAuDate(iso) {
  if (!iso) return "";
  const d = new Date(iso);
  if (Number.isNaN(d.getTime())) return String(iso);
  return d.toLocaleDateString("en-AU", {
    day: "numeric", month: "long", year: "numeric", timeZone: "Australia/Brisbane",
  });
}
function orgHeadcount(business) {
  return (business.org_structure || []).reduce((sum, r) => sum + (Number(r.headcount) || 0), 0);
}
function emptyWorkflow(id) {
  return { id: id || uid(), goal_name: "", who: "", process: "", problems: "", done: "", name: "", current: "", pain: null, comment: "" };
}
function normalizeWorkflow(w) {
  const x = w && typeof w === "object" ? w : {};
  const goal_name = String(x.goal_name || x.name || "").trim();
  const process = String(x.process || x.current || "").trim();
  const problems = String(x.problems || x.comment || "").trim();
  return {
    id: x.id || uid(),
    goal_name, who: String(x.who || "").trim(), process, problems,
    done: String(x.done || "").trim(),
    name: goal_name, current: process, pain: x.pain == null ? null : x.pain, comment: problems,
  };
}
function workflowComplete(w) {
  const n = normalizeWorkflow(w);
  return !!(n.goal_name && n.who && n.process && n.problems && n.done);
}
function canSubmit(business, workflows) {
  return filled(business.name)
    && filled(business.first_name)
    && filled(business.last_name)
    && isEmail(business.email)
    && filled(business.phone)
    && workflows.some((w) => filled(w.goal_name))
    && directorReady(business)
    && hasCompleteOrg(business)
    && (business.filler_is_director !== false || filled(business.job_title));
}
function splitPersonName(full) {
  const t = String(full || "").trim();
  if (!t) return { first_name: "", last_name: "" };
  const parts = t.split(/\s+/);
  return { first_name: parts[0], last_name: parts.slice(1).join(" ") };
}
function directorFromFiller(business) {
  const names = {
    first_name: String(business.first_name || "").trim(),
    last_name: String(business.last_name || "").trim(),
  };
  if (!names.first_name || !names.last_name) {
    const split = splitPersonName(business.contactName);
    if (!names.first_name) names.first_name = split.first_name;
    if (!names.last_name) names.last_name = split.last_name;
  }
  return {
    first_name: names.first_name,
    last_name: names.last_name,
    email: String(business.email || "").trim().toLowerCase(),
    phone: String(business.phone || "").trim(),
  };
}
function directorReady(business) {
  const d = business.director || {};
  if (business.filler_is_director === true) {
    const copy = d.first_name || d.email ? d : directorFromFiller(business);
    return !!(copy.first_name && copy.last_name && isEmail(copy.email) && filled(copy.phone));
  }
  if (business.filler_is_director === false) {
    return !!(filled(d.first_name) && filled(d.last_name) && isEmail(d.email) && filled(d.phone) && filled(business.job_title));
  }
  return false;
}
function withFillerName(business, patch) {
  const next = syncFillerName({ ...business, ...patch });
  if (next.filler_is_director === true) next.director = directorFromFiller(next);
  return next;
}

const BIZ_STEPS = [
  { key: "name", q: GREETING, follow: "What's the business name?" },
  { key: "first_name", follow: "What's your first name?" },
  { key: "last_name", follow: "And your last name?" },
  { key: "email", follow: "What's the best email to reach you?" },
  { key: "phone", follow: "What's the best phone number to reach you?" },
  { key: "industry", follow: "What industry are you in?" },
  { key: "country", follow: "Which country are you operating in?" },
  { key: "product", follow: "Which AYG product are we adjusting, or is this a new custom build?" },
];
const WF_STEPS = [
  { key: "goal_name", follow: "What's the goal, and what do you call this procedure?" },
  { key: "who", follow: "Who normally performs it? Name and job title." },
  { key: "process", follow: "How is that done today? The current state of the process." },
  { key: "problems", follow: "What's wrong with the current setup?" },
  { key: "done", follow: "What does DONE look like for this workflow?" },
];
const WORKFLOW_WHAT = "A workflow is a repeatable procedure your team does, like onboarding a client, approving a timesheet, or chasing an overdue invoice.";
const WORKFLOW_EXAMPLE = "Example: Onboarding a new NDIS participant, from enquiry to first booked shift.";
const WORKFLOW_INTRO = WORKFLOW_WHAT + " " + WORKFLOW_EXAMPLE;
const ORG_NEED_AT_LEAST_ONE = "Org structure is required. We need at least one role: a job title, how many people (1 or more), and a rough salary. Sole trader is one row. What is the first job title?";
const ORG_NEED_SALARY = "A rough salary figure is needed, like about 75k a year. Sole trader, what you take out of the business is fine.";
const SKIP_RE = /^(no|nope|nah|skip|none|n\/a|na|later|prefer not)[\s.!]*$/i;
const YES_RE = /^(yes|yeah|yep|yup|sure|another|one more)/i;
const NO_MORE_RE = /^(no|nope|nah|that's (all|it|the lot)|thats (all|it|the lot)|all good|done|finished|the lot)/i;
const YES_DIR_RE = /^(yes|yeah|yep|yup|sure|i am|i'm|im a director|i am a director|director)[\s.!]*$/i;
const NO_DIR_RE = /^(no|nope|nah|not me|i'm not|im not|not a director|no i am not|no i'm not)[\s.!]*$/i;
const BARE_YES_RE = /^(yes|yeah|yep|yup|sure|please|another|one more)[\s.!]*$/i;
const SOLE_TRADER_RE = /sole trader|just me\b|only me\b|one[- ]person|owner[- ]operator/i;
const NO_TOOLS_RE = /^(none|nothing|no tools|we don't|we dont|don't use|dont use|skip)[\s.!]*$/i;

function missingDirector(business) {
  if (business.filler_is_director == null) {
    return { key: "filler_is_director", follow: "Are you a director of the business?" };
  }
  if (business.filler_is_director === true) return null;
  if (!filled(business.job_title)) return { key: "job_title", follow: "What's your job title?" };
  const d = business.director || {};
  if (!filled(d.first_name)) return { key: "director", directorKey: "first_name", follow: "What's the director's first name?" };
  if (!filled(d.last_name)) return { key: "director", directorKey: "last_name", follow: "And the director's last name?" };
  if (!isEmail(d.email)) return { key: "director", directorKey: "email", follow: "What's the director's email?" };
  if (!filled(d.phone)) return { key: "director", directorKey: "phone", follow: "And a phone number for the director?" };
  return null;
}
function missingBiz(business) {
  for (const s of BIZ_STEPS) {
    if (s.key === "industry") {
      const dir = missingDirector(business);
      if (dir) return dir;
    }
    if (filled(business[s.key])) continue;
    if (s.optional && business.skipped && business.skipped[s.key]) continue;
    return s;
  }
  return missingDirector(business);
}
function workflowStartReply(prefix) {
  return (prefix ? prefix + " " : "") + WORKFLOW_INTRO + " " + WF_STEPS[0].follow;
}

function missingWf(w) {
  for (const s of WF_STEPS) {
    if (!filled(w[s.key])) return s;
  }
  return null;
}

/** Used when /api/scope-chat is unreachable so the live card still fills. */
function localTurn(userText, state) {
  const text = String(userText || "").trim();
  const business = hydrateBusiness(state.business);
  let workflows = state.workflows.map(normalizeWorkflow);
  let awaitingMore = !!state.awaitingMore;
  let awaitingMoreOrg = !!state.awaitingMoreOrg;
  let awaitingMorePlatforms = !!state.awaitingMorePlatforms;
  const miss = missingBiz(business);
  if (miss) {
    if (miss.key === "email") {
      const m = text.match(/[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}/);
      if (!m) return { reply: "That email does not look right. Could you type it again?", patches: { business } };
      business.email = m[0].toLowerCase();
    } else if (miss.key === "phone" && SKIP_RE.test(text)) {
      return { reply: "A phone number is required. What's the best number to reach you?", patches: { business } };
    } else if (miss.key === "last_name" && SKIP_RE.test(text)) {
      return { reply: "Last name is required. What is your last name?", patches: { business } };
    } else if (miss.key === "job_title" && SKIP_RE.test(text)) {
      return { reply: "Your job title is required. What is your job title at the business?", patches: { business } };
    } else if (miss.key === "filler_is_director") {
      if (YES_DIR_RE.test(text)) {
        business.filler_is_director = true;
        business.director = directorFromFiller(business);
      } else if (NO_DIR_RE.test(text)) {
        business.filler_is_director = false;
        business.director = { first_name: "", last_name: "", email: "", phone: "" };
      } else {
        return { reply: "Just a yes or no is fine. Are you a director of the business?", patches: { business } };
      }
    } else if (miss.directorKey) {
      const director = { ...(business.director || { first_name: "", last_name: "", email: "", phone: "" }) };
      if (miss.directorKey === "email") {
        const m = text.match(/[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}/);
        if (!m) return { reply: "That director email does not look right. Could you type it again?", patches: { business } };
        director.email = m[0].toLowerCase();
      } else {
        director[miss.directorKey] = text;
      }
      business.director = director;
    } else if (miss.key === "first_name") {
      const names = splitPersonName(text);
      business.first_name = names.first_name;
      if (names.last_name && !business.last_name) business.last_name = names.last_name;
      syncFillerName(business);
    } else if (miss.key === "last_name") {
      business.last_name = text;
      syncFillerName(business);
    } else {
      business[miss.key] = text;
    }
    const still = missingBiz(business);
    if (still) return { reply: still.follow, patches: { business }, readyForReview: false };
    if (!business.org_structure.length) business.org_structure = [emptyRole()];
    return {
      reply: "Nice, " + business.name + " is on the card. Let's map the org structure. What's the first job title? For example Support Worker or Roster Manager.",
      patches: { business, workflows, awaitingMoreOrg: false },
      readyForReview: false,
    };
  }

  if (!orgSettled(business) || awaitingMoreOrg) {
    const org = localOrgTurn(text, business, awaitingMoreOrg);
    return {
      reply: org.reply,
      patches: { business: org.business, awaitingMoreOrg: org.awaitingMoreOrg, awaitingMorePlatforms: org.awaitingMorePlatforms },
      readyForReview: false,
    };
  }

  if (!platformsSettled(business) || awaitingMorePlatforms) {
    const plat = localPlatformTurn(text, business, awaitingMorePlatforms);
    return {
      reply: plat.reply,
      patches: { business: plat.business, awaitingMorePlatforms: plat.awaitingMorePlatforms, workflows: plat.workflows || workflows },
      readyForReview: false,
    };
  }

  if (!workflows.length) workflows = [emptyWorkflow()];
  let open = workflows.find((w) => !workflowComplete(w));
  if (!open) {
    if (YES_RE.test(text) && !NO_MORE_RE.test(text)) {
      workflows = [...workflows, emptyWorkflow()];
      return { reply: WF_STEPS[0].follow, patches: { workflows, awaitingMore: false }, readyForReview: false };
    }
    if (NO_MORE_RE.test(text) || !YES_RE.test(text)) {
      return {
        reply: "That gives us a solid picture. Have a look at the scope card and confirm it is accurate. Once you confirm, we will send it through for Gus to review. Typical custom delivery is about 28 days, sometimes sooner.",
        patches: { workflows, readyForReview: true, awaitingMore: false },
        readyForReview: true,
      };
    }
  }
  const field = missingWf(open);
  open = normalizeWorkflow({ ...open, [field.key]: text });
  workflows = workflows.map((w) => (w.id === open.id ? open : w));
  if (!workflowComplete(open)) {
    return { reply: missingWf(open).follow, patches: { workflows }, readyForReview: false };
  }
  return {
    reply: "Got it. Is there another workflow or procedure we should map, or is that the lot?",
    patches: { workflows, awaitingMore: true },
    readyForReview: false,
  };
}

function localOrgTurn(text, business, awaitingMoreOrg) {
  business.org_structure = (business.org_structure || []).map(normalizeRole);
  if (awaitingMoreOrg) {
    if (NO_MORE_RE.test(text) || SKIP_RE.test(text)) {
      if (!business.org_structure.some(roleComplete)) {
        business.org_structure_skipped = false;
        business.org_structure_complete = false;
        if (!business.org_structure.length) business.org_structure = [emptyRole()];
        return {
          business,
          awaitingMoreOrg: false,
          reply: ORG_NEED_AT_LEAST_ONE,
        };
      }
      business.org_structure_complete = true;
      business.org_structure_skipped = false;
      business.org_structure = business.org_structure.filter((r) => r.job_title);
      if (!business.platforms.length) business.platforms = [emptyPlatform()];
      return {
        business,
        awaitingMoreOrg: false,
        awaitingMorePlatforms: false,
        reply: "Got it. Which tools or platforms are you using today? CRM, payroll, rostering, email, spreadsheets. Name as many as you like.",
      };
    }
    const title = BARE_YES_RE.test(text) ? "" : text;
    const row = emptyRole();
    if (title) row.job_title = title;
    if (SOLE_TRADER_RE.test(text)) {
      row.job_title = row.job_title && !SOLE_TRADER_RE.test(row.job_title) ? row.job_title : "Owner / Sole trader";
      row.headcount = 1;
    }
    business.org_structure.push(row);
    return {
      business,
      awaitingMoreOrg: false,
      reply: row.job_title ? "How many people sit in that role?" : "What's the next job title?",
    };
  }
  if (!business.org_structure.length) business.org_structure.push(emptyRole());
  let open = business.org_structure.find((r) => !roleComplete(r));
  if (!open) {
    return { business, awaitingMoreOrg: true, reply: "Any other roles on the org chart?" };
  }
  if (!filled(open.job_title)) {
    if (SOLE_TRADER_RE.test(text)) {
      open.job_title = "Owner / Sole trader";
      open.headcount = 1;
      return { business, awaitingMoreOrg: false, reply: "Roughly what do you take out of the business a year, in Australian dollars? A ballpark is fine." };
    }
    if ((SKIP_RE.test(text) || NO_MORE_RE.test(text)) && !business.org_structure.some(roleComplete)) {
      business.org_structure_skipped = false;
      business.org_structure_complete = false;
      return {
        business,
        awaitingMoreOrg: false,
        reply: ORG_NEED_AT_LEAST_ONE,
      };
    }
    open.job_title = text;
    return { business, reply: "How many people sit in that role?" };
  }
  if (!(Number(open.headcount) >= 1)) {
    const n = parseRoughNumber(text) || (SOLE_TRADER_RE.test(text) ? 1 : null);
    if (!n || n < 1) return { business, reply: "How many people sit in that role? A number is best." };
    open.headcount = n;
    return { business, reply: "Roughly what does that role earn per person, in Australian dollars a year? A ballpark is fine." };
  }
  if (SKIP_RE.test(text)) {
    return { business, reply: ORG_NEED_SALARY };
  } else {
    const amount = parseRoughNumber(text, { money: true });
    if (amount == null) return { business, reply: "A rough figure is fine, like about 75k a year." };
    open.approx_salary = amount;
    open.salary_period = detectPeriod(text, "annual");
  }
  return { business, awaitingMoreOrg: true, reply: "Any other roles on the org chart?" };
}

function localPlatformTurn(text, business, awaitingMorePlatforms) {
  business.platforms = (business.platforms || []).map(normalizePlatform);
  if (awaitingMorePlatforms) {
    if (NO_MORE_RE.test(text) || SKIP_RE.test(text) || NO_TOOLS_RE.test(text)) {
      business.platforms_complete = true;
      business.platforms = business.platforms.filter((p) => p.name);
      if (!business.platforms.length) {
        business.platforms_skipped = true;
        business.platforms_note = "No platforms captured during discovery.";
      }
      return {
        business,
        awaitingMorePlatforms: false,
        reply: workflowStartReply(""),
      };
    }
    const names = text.split(/\s*(?:,|&|\band\b)\s*/i).map((s) => s.trim()).filter(Boolean);
    (names.length ? names : [text]).forEach((name) => {
      const row = emptyPlatform();
      row.name = BARE_YES_RE.test(name) ? "" : name;
      if (row.name) business.platforms.push(row);
    });
    const open = business.platforms.find((p) => p.name && !platformComplete(p));
    if (!open) return { business, awaitingMorePlatforms: true, reply: "Any other tools or platforms we should capture?" };
    return { business, awaitingMorePlatforms: false, reply: "Roughly what do you pay for " + open.name + ", in Australian dollars? Monthly or annual is fine." };
  }
  if (!business.platforms.length) business.platforms.push(emptyPlatform());
  let open = business.platforms.find((p) => !platformComplete(p));
  if (!open) return { business, awaitingMorePlatforms: true, reply: "Any other tools or platforms we should capture?" };
  if (!filled(open.name)) {
    if (SKIP_RE.test(text) || NO_TOOLS_RE.test(text)) {
      business.platforms_skipped = true;
      business.platforms_complete = true;
      business.platforms_note = "No platforms captured during discovery.";
      business.platforms = [];
      return { business, awaitingMorePlatforms: false, reply: workflowStartReply("No worries.") };
    }
    const names = text.split(/\s*(?:,|&|\band\b)\s*/i).map((s) => s.trim()).filter(Boolean);
    open.name = names[0] || text;
    names.slice(1).forEach((name) => {
      const row = emptyPlatform();
      row.name = name;
      business.platforms.push(row);
    });
    return { business, reply: "Roughly what do you pay for " + open.name + ", in Australian dollars? Monthly or annual is fine." };
  }
  if (open.approx_spend === "" && !open.spend_skipped) {
    if (SKIP_RE.test(text)) {
      open.spend_skipped = true;
      open.spend_period = "unknown";
    } else {
      const amount = parseRoughNumber(text);
      if (amount == null) return { business, reply: "A rough figure is fine. Monthly or annual, in Australian dollars." };
      open.approx_spend = amount;
      open.spend_period = detectPeriod(text, "unknown");
      if (open.spend_period === "unknown") {
        return { business, reply: "Is that monthly or annual?" };
      }
    }
    return { business, reply: "How many users or seats on " + open.name + "?" };
  }
  if (open.spend_period === "unknown" && open.approx_spend !== "" && !open.spend_skipped) {
    const period = detectPeriod(text, "");
    if (period === "monthly" || period === "annual") open.spend_period = period;
    else if (SKIP_RE.test(text)) open.spend_period = "unknown";
    else return { business, reply: "Monthly or annual is perfect." };
    return { business, reply: "How many users or seats on " + open.name + "?" };
  }
  if (SKIP_RE.test(text)) open.users_skipped = true;
  else {
    const n = parseRoughNumber(text);
    if (n == null || n < 0) return { business, reply: "How many users or seats? A number is best." };
    open.users = n;
  }
  const still = business.platforms.find((p) => p.name && !platformComplete(p));
  if (still) {
    return { business, reply: "Roughly what do you pay for " + still.name + ", in Australian dollars? Monthly or annual is fine." };
  }
  return { business, awaitingMorePlatforms: true, reply: "Any other tools or platforms we should capture?" };
}

function mergePatches(state, patches) {
  if (!patches || typeof patches !== "object") return state;
  const next = { ...state };
  if (patches.business && typeof patches.business === "object") {
    next.business = hydrateBusiness({ ...next.business, ...patches.business });
  }
  if (Array.isArray(patches.org_structure)) {
    next.business = hydrateBusiness({ ...next.business, org_structure: patches.org_structure });
  }
  if (Array.isArray(patches.platforms)) {
    next.business = hydrateBusiness({ ...next.business, platforms: patches.platforms });
  }
  if (Array.isArray(patches.workflows)) {
    next.workflows = patches.workflows.map(normalizeWorkflow);
  } else if (patches.workflow && typeof patches.workflow === "object") {
    const incoming = normalizeWorkflow(patches.workflow);
    const idx = next.workflows.findIndex((w) => w.id === incoming.id);
    if (idx >= 0) {
      const copy = next.workflows.slice();
      copy[idx] = normalizeWorkflow({ ...copy[idx], ...incoming });
      next.workflows = copy;
    } else {
      next.workflows = [...next.workflows, incoming];
    }
  }
  if (Array.isArray(patches.automations)) next.automations = patches.automations;
  if (typeof patches.notes === "string") next.notes = patches.notes;
  if (typeof patches.readyForReview === "boolean") next.readyForReview = patches.readyForReview;
  if (typeof patches.awaitingMore === "boolean") next.awaitingMore = patches.awaitingMore;
  if (typeof patches.awaitingMoreOrg === "boolean") next.awaitingMoreOrg = patches.awaitingMoreOrg;
  if (typeof patches.awaitingMorePlatforms === "boolean") next.awaitingMorePlatforms = patches.awaitingMorePlatforms;
  return next;
}

function loadSession() {
  try {
    const raw = sessionStorage.getItem(STORAGE_KEY);
    if (!raw) return null;
    const d = JSON.parse(raw);
    if (!d || typeof d !== "object") return null;
    return d;
  } catch (_) { return null; }
}
function saveSession(payload) {
  try { sessionStorage.setItem(STORAGE_KEY, JSON.stringify(payload)); } catch (_) { /* ignore */ }
}
function clearSession() {
  try { sessionStorage.removeItem(STORAGE_KEY); } catch (_) { /* ignore */ }
}

function useIsMobile(bp = 900) {
  const [m, setM] = useState(typeof window !== "undefined" ? window.innerWidth <= bp : false);
  useEffect(() => {
    const onResize = () => setM(window.innerWidth <= bp);
    onResize();
    window.addEventListener("resize", onResize);
    return () => window.removeEventListener("resize", onResize);
  }, [bp]);
  return m;
}

let liveMicStop = null;
function stopAllMics() {
  if (typeof liveMicStop === "function") {
    const fn = liveMicStop;
    liveMicStop = null;
    fn();
  }
}

function isSpaceKey(e) {
  return e.key === " " || e.key === "Spacebar" || e.code === "Space";
}
function handleTypingKeyDown(e, extra) {
  if (isSpaceKey(e)) {
    e.stopPropagation();
    return;
  }
  if (typeof extra === "function") extra(e);
}

function DictationBox({ value, onChange, disabled, placeholder, rows = 3, inputStyle, ariaLabel, onKeyDown, compact }) {
  const [listening, setListening] = useState(false);
  const [voiceHint, setVoiceHint] = useState("");
  const taRef = useRef(null);
  const committedRef = useRef("");
  const langIndexRef = useRef(0);
  const listeningRef = useRef(false);
  const recogRef = useRef(null);
  const voiceOk = !!getSpeechRecognition();

  const stop = () => {
    const r = recogRef.current;
    if (r) {
      r.onend = null;
      r.onerror = null;
      r.onresult = null;
      try { r.stop(); } catch (_) { /* already stopped */ }
      recogRef.current = null;
    }
    if (liveMicStop && listeningRef.current) liveMicStop = null;
    listeningRef.current = false;
    setListening(false);
  };

  useEffect(() => () => { if (listeningRef.current) stop(); }, []);

  const start = () => {
    const Rec = getSpeechRecognition();
    if (!Rec || disabled) return;
    stopAllMics();
    const existing = taRef.current ? taRef.current.value : (value || "");
    committedRef.current = String(existing || "").replace(/\s+$/, "");
    const lang = VOICE_LANGS[langIndexRef.current] || "en-AU";
    const r = new Rec();
    r.lang = lang;
    r.continuous = true;
    r.interimResults = true;
    r.onresult = (ev) => {
      let interim = "";
      let committed = committedRef.current;
      for (let i = ev.resultIndex; i < ev.results.length; i++) {
        const chunk = ev.results[i][0].transcript;
        if (ev.results[i].isFinal) committed = joinTranscript(committed, chunk.trim());
        else interim += chunk;
      }
      committedRef.current = committed;
      onChange(joinTranscript(committed, interim));
    };
    r.onerror = (ev) => {
      if (ev.error === "language-not-supported" && langIndexRef.current < VOICE_LANGS.length - 1) {
        langIndexRef.current += 1;
        start();
        return;
      }
      recogRef.current = null;
      listeningRef.current = false;
      setListening(false);
      if (liveMicStop) liveMicStop = null;
      if (ev.error === "not-allowed") setVoiceHint("Microphone blocked. Allow mic access, then tap the mic again.");
      else if (ev.error === "no-speech") setVoiceHint("Didn't hear anything. Tap the mic and speak.");
      else if (ev.error && ev.error !== "aborted") setVoiceHint("Voice input stopped. Type instead if you like.");
    };
    r.onend = () => {
      recogRef.current = null;
      listeningRef.current = false;
      setListening(false);
      if (liveMicStop) liveMicStop = null;
      onChange(committedRef.current);
    };
    recogRef.current = r;
    listeningRef.current = true;
    liveMicStop = stop;
    setListening(true);
    setVoiceHint("");
    try { r.start(); }
    catch (e) {
      recogRef.current = null;
      listeningRef.current = false;
      liveMicStop = null;
      setListening(false);
      setVoiceHint("Could not start the microphone. Type instead.");
    }
  };

  const toggle = () => {
    if (!voiceOk || disabled) return;
    if (listening) stop();
    else start();
  };

  return (
    <div style={{ width: "100%" }}>
      <div style={styles.dictateRow}>
        {compact ? (
          <input
            ref={taRef}
            style={inputStyle}
            value={value}
            disabled={disabled}
            placeholder={listening ? "Listening…" : placeholder}
            aria-label={ariaLabel || placeholder}
            onKeyDown={(e) => handleTypingKeyDown(e, onKeyDown)}
            onChange={(e) => {
              onChange(e.target.value);
              committedRef.current = e.target.value;
            }}
          />
        ) : (
        <textarea
          ref={taRef}
          style={inputStyle}
          rows={rows}
          value={value}
          disabled={disabled}
          placeholder={listening ? "Listening…" : placeholder}
          aria-label={ariaLabel || placeholder}
          onKeyDown={(e) => handleTypingKeyDown(e, onKeyDown)}
          onChange={(e) => {
            onChange(e.target.value);
            committedRef.current = e.target.value;
          }}
        />
        )}
        <button
          type="button"
          className={"ayg-mic" + (listening ? " is-on" : "")}
          data-ayg-mic="1"
          data-listening={listening ? "true" : "false"}
          style={{ ...styles.micBtn, ...(listening ? styles.micBtnOn : {}), ...(!voiceOk ? styles.micBtnOff : {}) }}
          disabled={!voiceOk || disabled}
          title={voiceOk ? (listening ? "Stop listening" : "Speak your answer") : VOICE_UNSUPPORTED}
          aria-label={voiceOk ? (listening ? "Stop listening" : "Speak your answer") : VOICE_UNSUPPORTED}
          aria-pressed={listening}
          onClick={toggle}
        >
          <Mic size={18} />
        </button>
      </div>
      {(listening || voiceHint) && (
        <p style={listening ? styles.listenHint : styles.hint} aria-live="polite">
          {listening ? "Listening…" : voiceHint}
        </p>
      )}
    </div>
  );
}

function ScopeBuilder() {
  const stored = typeof window !== "undefined" ? loadSession() : null;
  const [phase, setPhase] = useState(stored?.phase || "chat"); // chat | review | thanks
  const [messages, setMessages] = useState(stored?.messages || [
    { role: "assistant", content: GREETING },
  ]);
  const [business, setBusiness] = useState(hydrateBusiness(stored?.business));
  const [workflows, setWorkflows] = useState(
    Array.isArray(stored?.workflows) && stored.workflows.length
      ? stored.workflows.map(normalizeWorkflow)
      : []
  );
  const [automations, setAutomations] = useState(Array.isArray(stored?.automations) ? stored.automations : []);
  const [notes, setNotes] = useState(stored?.notes || "");
  const [readyForReview, setReadyForReview] = useState(!!stored?.readyForReview);
  const [awaitingMore, setAwaitingMore] = useState(!!stored?.awaitingMore);
  const [awaitingMoreOrg, setAwaitingMoreOrg] = useState(!!stored?.awaitingMoreOrg);
  const [awaitingMorePlatforms, setAwaitingMorePlatforms] = useState(!!stored?.awaitingMorePlatforms);
  const [draft, setDraft] = useState("");
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState("");
  const [confirmed, setConfirmed] = useState(false);
  const [saveState, setSaveState] = useState("idle");
  const [quoteRef, setQuoteRef] = useState(stored?.quoteRef || "");
  const [quoteResult, setQuoteResult] = useState(stored?.quote || null);
  const [quoteDecision, setQuoteDecision] = useState(null);
  const [rejectReason, setRejectReason] = useState("");
  const [deciding, setDeciding] = useState(false);
  const logRef = useRef(null);
  const submittedRef = useRef(!!stored?.quoteRef);
  const isMobile = useIsMobile();

  const scopeSnapshot = { business, workflows, automations, notes, readyForReview, awaitingMore, awaitingMoreOrg, awaitingMorePlatforms };

  useEffect(() => {
    if (phase === "thanks") return;
    saveSession({ phase, messages, business, workflows, automations, notes, readyForReview, awaitingMore, awaitingMoreOrg, awaitingMorePlatforms, quoteRef });
  }, [phase, messages, business, workflows, automations, notes, readyForReview, awaitingMore, awaitingMoreOrg, awaitingMorePlatforms, quoteRef]);

  useEffect(() => {
    if (logRef.current) logRef.current.scrollTop = logRef.current.scrollHeight;
  }, [messages, busy, phase]);

  useEffect(() => {
    if (phase !== "chat") stopAllMics();
  }, [phase]);

  const sendTurn = async (text) => {
    const content = String(text || "").trim();
    if (!content || busy) return;
    stopAllMics();
    setError("");
    setDraft("");
    const nextMessages = [...messages, { role: "user", content }];
    setMessages(nextMessages);
    setBusy(true);
    try {
      let data = null;
      try {
        const res = await fetch(SCOPE_CHAT_API, {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ messages: nextMessages, scope: scopeSnapshot }),
        });
        const parsed = await res.json().catch(() => ({}));
        if (res.ok && parsed.reply) data = parsed;
      } catch (_) { /* fall through to local turn */ }
      if (!data) data = localTurn(content, scopeSnapshot);
      const merged = mergePatches({ business, workflows, automations, notes, readyForReview, awaitingMore, awaitingMoreOrg, awaitingMorePlatforms }, data.patches);
      setBusiness(merged.business);
      setWorkflows(merged.workflows);
      setAutomations(merged.automations);
      setNotes(merged.notes);
      setAwaitingMore(!!merged.awaitingMore);
      setAwaitingMoreOrg(!!merged.awaitingMoreOrg);
      setAwaitingMorePlatforms(!!merged.awaitingMorePlatforms);
      const ready = !!(data.readyForReview || merged.readyForReview);
      setReadyForReview(ready);
      setMessages([...nextMessages, { role: "assistant", content: data.reply }]);
      if (ready) setPhase("review");
    } catch (e) {
      setError("Could not reach the assistant just now. You can keep filling the scope card on the right, then review and send.");
      setMessages([...nextMessages, {
        role: "assistant",
        content: "I've got that. Keep going on the scope card if you like, or try sending again.",
      }]);
    }
    setBusy(false);
  };

  const addWorkflow = () => setWorkflows((w) => [...w, emptyWorkflow()]);
  const updateWorkflow = (id, patch) => setWorkflows((list) => list.map((x) => {
    if (x.id !== id) return x;
    const next = { ...x, ...patch };
    if (patch.goal_name != null) next.name = patch.goal_name;
    if (patch.process != null) next.current = patch.process;
    if (patch.problems != null) next.comment = patch.problems;
    return next;
  }));
  const removeWorkflow = (id) => setWorkflows((w) => w.filter((x) => x.id !== id));
  const setDirector = (patch) => setBusiness((b) => ({
    ...b,
    director: { first_name: "", last_name: "", email: "", phone: "", ...(b.director || {}), ...patch },
  }));
  const setFillerIsDirector = (yes) => setBusiness((b) => {
    const next = { ...b, filler_is_director: yes };
    next.director = yes ? directorFromFiller(next) : { first_name: "", last_name: "", email: "", phone: "", ...(b.director || {}) };
    return next;
  });
  const addRole = () => setBusiness((b) => ({ ...b, org_structure: [...(b.org_structure || []), emptyRole()] }));
  const updateRole = (id, patch) => setBusiness((b) => ({
    ...b,
    org_structure: (b.org_structure || []).map((r) => (r.id === id ? { ...r, ...patch } : r)),
  }));
  const removeRole = (id) => setBusiness((b) => ({ ...b, org_structure: (b.org_structure || []).filter((r) => r.id !== id) }));
  const toggleTool = (name) => setBusiness((b) => {
    const platforms = (b.platforms || []).map(normalizePlatform);
    const idx = platforms.findIndex((p) => p.name.toLowerCase() === name.toLowerCase());
    if (idx >= 0) return { ...b, platforms: platforms.filter((_, i) => i !== idx), tools: platforms.filter((_, i) => i !== idx).map((p) => p.name) };
    return { ...b, platforms: [...platforms, normalizePlatform({ name })], tools: [...platforms.map((p) => p.name), name] };
  });
  const addPlatform = () => setBusiness((b) => ({ ...b, platforms: [...(b.platforms || []), emptyPlatform()] }));
  const updatePlatform = (id, patch) => setBusiness((b) => {
    const platforms = (b.platforms || []).map((p) => (p.id === id ? { ...p, ...patch } : p));
    return { ...b, platforms, tools: platforms.map((p) => p.name).filter(Boolean) };
  });
  const removePlatform = (id) => setBusiness((b) => {
    const platforms = (b.platforms || []).filter((p) => p.id !== id);
    return { ...b, platforms, tools: platforms.map((p) => p.name).filter(Boolean) };
  });

  const submitScope = async () => {
    if (submittedRef.current) return;
    if (!canSubmit(business, workflows) || !confirmed) return;
    submittedRef.current = true;
    setSaveState("saving");
    setError("");
    try {
      const res = await fetch(SCOPE_API, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          business,
          workflows: workflows.map(normalizeWorkflow),
          automations,
          notes,
          messages,
          confirmed: true,
        }),
      });
      const data = await res.json().catch(() => ({}));
      if (!res.ok || !data.success) throw new Error(data.error || "save failed");
      setQuoteRef(data.quote_ref || "");
      setQuoteResult(data.quote || null);
      setSaveState("saved");
      setPhase("thanks");
      clearSession();
      saveSession({
        phase: "thanks",
        quoteRef: data.quote_ref || "",
        quote: data.quote || null,
        business,
        workflows,
      });
    } catch (e) {
      submittedRef.current = false;
      setSaveState("error");
      setError("We could not send that just now. Check your email on the card and try again.");
    }
  };

  const startOver = () => {
    stopAllMics();
    clearSession();
    setPhase("chat");
    setMessages([{ role: "assistant", content: GREETING }]);
    setBusiness(emptyBusiness());
    setWorkflows([]);
    setAutomations([]);
    setNotes("");
    setReadyForReview(false);
    setAwaitingMore(false);
    setAwaitingMoreOrg(false);
    setAwaitingMorePlatforms(false);
    setConfirmed(false);
    setSaveState("idle");
    setQuoteRef("");
    setQuoteResult(null);
    setQuoteDecision(null);
    setRejectReason("");
    setDeciding(false);
    setError("");
    setDraft("");
    submittedRef.current = false;
  };

  const decideQuote = async (action) => {
    if (!quoteResult || !quoteResult.share_token || deciding) return;
    setDeciding(true);
    try {
      const res = await fetch("/api/scope-quote", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ token: quoteResult.share_token, action, reason: rejectReason }),
      });
      const data = await res.json().catch(() => ({}));
      if (data.expired) { setQuoteDecision("expired"); return; }
      if (!res.ok && !data.decision) throw new Error(data.error || "failed");
      setQuoteDecision(data.decision || (action === "accept" ? "accepted" : "rejected"));
    } catch (e) {
      setQuoteDecision("error");
    } finally {
      setDeciding(false);
    }
  };

  const progress = phase === "thanks" ? 100
    : phase === "review" ? 82
    : filled(business.email) && (business.org_structure || []).some((r) => filled(r.job_title)) && workflows.some(workflowComplete) ? 72
    : filled(business.email) && (business.org_structure || []).some((r) => filled(r.job_title)) ? 52
    : filled(business.email) ? 36
    : filled(business.name) ? 18
    : 8;

  const S = styles;

  return (
    <div style={S.app}>
      <style>{`
        @keyframes pulse { 0%,100%{opacity:.45} 50%{opacity:1} }
        @keyframes slideUp { from{opacity:0;transform:translateY(10px)} to{opacity:1;transform:translateY(0)} }
        @keyframes ayg-mic-pulse {
          0%,100% { box-shadow: 0 0 0 0 rgba(224,94,31,.42); }
          50% { box-shadow: 0 0 0 8px rgba(224,94,31,0); }
        }
        * { -webkit-tap-highlight-color: transparent; }
        input:focus, textarea:focus, select:focus { border-color:${C.accent} !important; box-shadow: 0 0 0 3px ${C.accentSoft}; }
        .ayg-btn { transition: opacity .15s ease, transform .08s ease, background .15s ease, box-shadow .15s ease; }
        .ayg-btn:hover:not(:disabled){ opacity:.92 }
        .ayg-btn:active:not(:disabled){ transform: translateY(1px) }
        .ayg-chip:hover{ border-color:${C.accent} !important }
        .ayg-mic { transition: background .15s ease, color .15s ease, border-color .15s ease; }
        .ayg-mic:hover:not(:disabled){ border-color:${C.accent}; color:${C.accentDeep}; }
        .ayg-mic.is-on { animation: ayg-mic-pulse 1.2s ease-in-out infinite; }
        select option { background:${C.surface}; color:${C.ink}; }
        ::placeholder { color:#8a8681; }
        .ayg-log::-webkit-scrollbar { width: 8px; }
        .ayg-log::-webkit-scrollbar-thumb { background: ${C.lineSoft}; border-radius: 8px; }
        button:disabled { cursor: default; }
        input[type=checkbox] { accent-color: ${C.accent}; width: 16px; height: 16px; margin-top: 2px; flex-shrink: 0; }
      `}</style>

      <div style={S.header}>
        <div style={S.badge}>AYG Discovery</div>
        <h1 style={S.h1}>Scope <em style={S.h1em}>Builder</em></h1>
        <p style={S.subtitle}>
          {phase === "thanks"
            ? (quoteResult
              ? "Your priced quote is ready. Approve or reject below."
              : "Thanks. Gus will review this before any quote goes out.")
            : business.name
              ? "Mapping the work with " + business.name
              : "Talk it through. We fill the scope as you go."}
        </p>
      </div>

      <div style={S.progressTrack}>
        <div style={{ ...S.progressFill, width: progress + "%" }} />
      </div>

      {phase === "thanks" ? (
        <ThanksScreen
          quoteRef={quoteRef}
          quote={quoteResult}
          business={business}
          decision={quoteDecision}
          deciding={deciding}
          rejectReason={rejectReason}
          onReason={setRejectReason}
          onAccept={() => decideQuote("accept")}
          onReject={() => decideQuote("reject")}
          onReset={startOver}
        />
      ) : phase === "review" ? (
        <ReviewScreen
          business={business}
          workflows={workflows}
          automations={automations}
          notes={notes}
          confirmed={confirmed}
          setConfirmed={setConfirmed}
          saveState={saveState}
          error={error}
          onBack={() => setPhase("chat")}
          onSubmit={submitScope}
          canSend={canSubmit(business, workflows)}
          isMobile={isMobile}
        />
      ) : (
        <>
        <div style={{ ...S.shell, ...(isMobile ? S.shellMobile : {}) }}>
          <div style={S.chatCol}>
            <div style={S.chatHead}>
              <div>
                <div style={S.chatName}>Ethan</div>
                <div style={S.chatRole}>Ask Yr Grandpa · Brisbane</div>
              </div>
              {(readyForReview || canSubmit(business, workflows)) && (
                <button type="button" className="ayg-btn" style={S.reviewJump} onClick={() => setPhase("review")}>
                  Review scope
                </button>
              )}
            </div>
            <div className="ayg-log" ref={logRef} style={S.log} aria-live="polite">
              {messages.map((m, i) => (
                <div key={i} style={m.role === "user" ? S.bubbleYou : S.bubbleBot}>
                  {m.content}
                </div>
              ))}
              {busy && (
                <div style={S.bubbleBot} aria-label="Typing">
                  <span style={S.typing}>Ethan is typing</span>
                </div>
              )}
            </div>
            {error && <div style={S.errorBox}>{error}</div>}
            <form style={{ ...S.composer, ...(isMobile ? S.composerMobile : {}) }} onSubmit={(e) => { e.preventDefault(); sendTurn(draft); }}>
              <DictationBox
                value={draft}
                onChange={setDraft}
                disabled={busy}
                placeholder="Answer in your own words…"
                rows={2}
                inputStyle={S.composerInput}
                ariaLabel="Your reply"
                onKeyDown={(e) => {
                  if (isSpaceKey(e)) {
                    e.stopPropagation();
                    return;
                  }
                  if (e.isComposing || (e.nativeEvent && e.nativeEvent.isComposing)) return;
                  if (e.key === "Enter" && !e.shiftKey && !e.altKey && !e.ctrlKey && !e.metaKey) {
                    e.preventDefault();
                    sendTurn(e.target.value);
                  }
                }}
              />
              <button className="ayg-btn" style={S.sendBtn} type="submit" disabled={busy || !draft.trim()}>
                Send <Send size={14} style={{ marginLeft: 6 }} />
              </button>
            </form>
            <p style={S.hint}>No prices in this chat. After you confirm, a priced quote appears with Approve and Reject.</p>
          </div>

          <aside style={S.cardCol}>
            <div style={S.cardHead}>
              <span style={S.secK}>Live</span>
              <h2 style={S.cardTitle}>Scope card</h2>
            </div>
            <p style={S.cardLead}>Fills as you talk. Tweak anything that is off.</p>

            <div style={S.card}>
              <SectionLabel icon={<Layers size={14} />} t="Business" />
              <Field label="Business name" required>
                <input style={S.input} value={business.name}
                  onChange={(e) => setBusiness({ ...business, name: e.target.value })}
                  placeholder="Company name" />
              </Field>
              <div style={{ ...S.row2, ...(isMobile ? S.stack : {}) }}>
                <Field label="First name" required>
                  <input style={S.input} value={business.first_name || ""}
                    onChange={(e) => setBusiness(withFillerName(business, { first_name: e.target.value }))}
                    placeholder="First name" autoComplete="given-name" />
                </Field>
                <Field label="Last name" required>
                  <input style={S.input} value={business.last_name || ""}
                    onChange={(e) => setBusiness(withFillerName(business, { last_name: e.target.value }))}
                    placeholder="Last name" autoComplete="family-name" />
                </Field>
              </div>
              <div style={{ ...S.row2, ...(isMobile ? S.stack : {}) }}>
                <Field label="Email" required>
                  <input style={S.input} type="email" value={business.email}
                    onChange={(e) => setBusiness(withFillerName(business, { email: e.target.value }))}
                    placeholder="name@business.com.au" autoComplete="email" />
                </Field>
                <Field label="Phone" required>
                  <input style={S.input} type="tel" value={business.phone}
                    onChange={(e) => setBusiness(withFillerName(business, { phone: e.target.value }))}
                    placeholder="04XX XXX XXX" autoComplete="tel" inputMode="tel" />
                </Field>
              </div>
              <Field label="Country">
                <select style={S.input} value={business.country}
                  onChange={(e) => setBusiness({ ...business, country: e.target.value })}>
                  <option value="">Select one</option>
                  {COUNTRIES.map((c) => <option key={c} value={c}>{c}</option>)}
                </select>
              </Field>
              <Field label="Are you a director of the business?" required>
                <div style={S.chipWrap}>
                  {[{ v: true, l: "Yes, I am a director" }, { v: false, l: "No, I am not" }].map((opt) => (
                    <button key={String(opt.v)} type="button" className="ayg-chip"
                      onClick={() => setFillerIsDirector(opt.v)}
                      style={{ ...S.chip, ...(business.filler_is_director === opt.v ? S.chipOn : {}) }}>
                      {opt.l}
                    </button>
                  ))}
                </div>
              </Field>
              {business.filler_is_director === false && (
                <div>
                  <Field label="Your job title" required>
                    <input style={S.input} value={business.job_title || ""}
                      onChange={(e) => setBusiness({ ...business, job_title: e.target.value })}
                      placeholder="e.g. Operations manager" />
                  </Field>
                  <div style={{ ...S.row2, ...(isMobile ? S.stack : {}) }}>
                    <Field label="Director first name" required>
                      <input style={S.input} value={(business.director || {}).first_name || ""}
                        onChange={(e) => setDirector({ first_name: e.target.value })} placeholder="First name" />
                    </Field>
                    <Field label="Director last name" required>
                      <input style={S.input} value={(business.director || {}).last_name || ""}
                        onChange={(e) => setDirector({ last_name: e.target.value })} placeholder="Last name" />
                    </Field>
                  </div>
                  <div style={{ ...S.row2, ...(isMobile ? S.stack : {}) }}>
                    <Field label="Director email" required>
                      <input style={S.input} type="email" value={(business.director || {}).email || ""}
                        onChange={(e) => setDirector({ email: e.target.value })} placeholder="director@business.com.au" />
                    </Field>
                    <Field label="Director phone" required>
                      <input style={S.input} value={(business.director || {}).phone || ""}
                        onChange={(e) => setDirector({ phone: e.target.value })} placeholder="Phone" />
                    </Field>
                  </div>
                </div>
              )}
              {business.filler_is_director === true && (
                <p style={S.cardLead}>Director details copied from you: {(business.director || {}).first_name || ""} {(business.director || {}).last_name || ""}.</p>
              )}
              <Field label="Industry">
                <select style={S.input} value={business.industry}
                  onChange={(e) => setBusiness({ ...business, industry: e.target.value })}>
                  <option value="">Select one</option>
                  {INDUSTRIES.map((i) => <option key={i} value={i}>{i}</option>)}
                </select>
              </Field>
              <Field label="AYG product or custom build">
                <select style={S.input} value={business.product}
                  onChange={(e) => setBusiness({ ...business, product: e.target.value })}>
                  <option value="">Select one</option>
                  {AYG_PRODUCTS.map((p) => <option key={p} value={p}>{p}</option>)}
                </select>
              </Field>
            </div>

            <div style={S.card}>
              <SectionLabel icon={<Users size={14} />} t="Org structure" />
              <p style={S.cardLead}>Roles, headcount, and a rough salary each. Annual AUD unless you say otherwise.</p>
              {(business.org_structure || []).length === 0 && (
                <div style={S.empty}>Nothing mapped yet. Ethan will ask for job title, how many people, then a rough salary.</div>
              )}
              {(business.org_structure || []).map((r, i) => (
                <OrgRoleCard key={r.id || i} role={r} i={i} isMobile={isMobile} onChange={updateRole} onRemove={removeRole} />
              ))}
              {!!orgHeadcount(business) && (
                <p style={S.muteLine}>Total headcount on the card: {orgHeadcount(business)}</p>
              )}
              <button className="ayg-btn" style={S.addBtn} onClick={addRole}>
                <Plus size={15} style={{ marginRight: 8 }} /> Add a role
              </button>
            </div>

            <div style={S.card}>
              <SectionLabel icon={<Layers size={14} />} t="Platforms and spend" />
              <p style={S.cardLead}>Tools you already pay for, roughly what they cost, and how many seats.</p>
              <div style={S.chipWrap}>
                {TOOL_OPTIONS.map((t) => {
                  const on = (business.platforms || []).some((p) => p.name === t);
                  return (
                    <button key={t} type="button" className="ayg-chip"
                      onClick={() => toggleTool(t)}
                      style={{ ...S.chip, ...(on ? S.chipOn : {}) }}>
                      {t}
                    </button>
                  );
                })}
              </div>
              {(business.platforms || []).filter((p) => p.name || p.approx_spend !== "" || p.users !== "").map((p, i) => (
                <PlatformCard key={p.id || i} platform={p} isMobile={isMobile} onChange={updatePlatform} onRemove={removePlatform} />
              ))}
              <button className="ayg-btn" style={{ ...S.addBtn, marginTop: 12 }} onClick={addPlatform}>
                <Plus size={15} style={{ marginRight: 8 }} /> Add a platform
              </button>
            </div>

            <div style={S.card}>
              <SectionLabel icon={<Cog size={14} />} t="Workflows" />
              <p style={S.cardLead}>{WORKFLOW_WHAT}</p>
              <p style={S.cardLead}>{WORKFLOW_EXAMPLE}</p>
              {workflows.length === 0 && (
                <div style={S.empty}>Nothing mapped yet. Ethan will ask about each procedure in order: goal, who, current state, problems, done.</div>
              )}
              {workflows.map((w, i) => (
                <WorkflowCard key={w.id} w={w} i={i} onChange={updateWorkflow} onRemove={removeWorkflow} />
              ))}
              <button className="ayg-btn" style={S.addBtn} onClick={addWorkflow}>
                <Plus size={15} style={{ marginRight: 8 }} /> Add a workflow
              </button>
            </div>

            {(automations.length > 0 || notes) && (
              <div style={S.card}>
                <SectionLabel icon={<FileText size={14} />} t="Also captured" />
                {automations.map((a, i) => (
                  <p key={i} style={S.bullet}>
                    WHEN {a.trigger || "an event"}, THEN {a.action || a.detail || "an action"}.
                  </p>
                ))}
                {notes && <p style={S.body}>{notes}</p>}
              </div>
            )}

            <Field label="Anything else we should know">
              <DictationBox
                value={notes}
                onChange={setNotes}
                placeholder="Deadlines, compliance, integrations."
                rows={3}
                inputStyle={S.textarea}
                ariaLabel="Anything else we should know"
              />
            </Field>
          </aside>
        </div>
        {(readyForReview || canSubmit(business, workflows)) && (
          <div style={S.chatSubmitSticky} role="region" aria-label="Review and submit">
            <span style={{ ...S.cardLead, margin: 0 }}>Scope is ready. Review it, then Submit.</span>
            <button type="button" className="ayg-btn" style={S.primaryBtn} onClick={() => setPhase("review")}>
              Review and submit
            </button>
          </div>
        )}
        </>
      )}
    </div>
  );
}

function OrgRoleCard({ role, i, isMobile, onChange, onRemove }) {
  const done = roleComplete(role);
  return (
    <div style={{ ...styles.wfCard, ...(done ? styles.wfCardDone : {}) }}>
      <div style={styles.itemHead}>
        <span style={styles.itemNum}>{String(i + 1).padStart(2, "0")}</span>
        <div style={{ flex: 1, minWidth: 0 }}>
          <DictationBox
            compact
            value={role.job_title}
            onChange={(v) => onChange(role.id, { job_title: v })}
            placeholder="Job title"
            inputStyle={styles.input}
            ariaLabel="Job title"
          />
        </div>
        <button style={styles.iconBtn} onClick={() => onRemove(role.id)} title="Remove"><Trash2 size={15} /></button>
      </div>
      <div style={{ ...styles.row3, ...(isMobile ? styles.stack : {}) }}>
        <Field label="Headcount">
          <input style={styles.input} type="number" min="1" inputMode="numeric" value={role.headcount}
            onChange={(e) => onChange(role.id, { headcount: e.target.value })} placeholder="How many" />
        </Field>
        <Field label="Approx salary">
          <DictationBox
            compact
            value={role.approx_salary === "" || role.approx_salary == null ? "" : String(role.approx_salary)}
            onChange={(v) => onChange(role.id, { approx_salary: v, salary_skipped: !String(v || "").trim() })}
            placeholder="e.g. 75k"
            inputStyle={styles.input}
            ariaLabel="Approximate salary"
          />
        </Field>
        <Field label="Period">
          <select style={styles.input} value={role.salary_period || "annual"}
            onChange={(e) => onChange(role.id, { salary_period: e.target.value })}>
            {SALARY_PERIODS.map((p) => <option key={p} value={p}>{p}</option>)}
          </select>
        </Field>
      </div>
    </div>
  );
}

function PlatformCard({ platform, isMobile, onChange, onRemove }) {
  const done = platformComplete(platform);
  return (
    <div style={{ ...styles.wfCard, ...(done ? styles.wfCardDone : {}), marginTop: 12 }}>
      <div style={styles.itemHead}>
        <div style={{ flex: 1, minWidth: 0 }}>
          <DictationBox
            compact
            value={platform.name}
            onChange={(v) => onChange(platform.id, { name: v })}
            placeholder="Platform or tool"
            inputStyle={styles.input}
            ariaLabel="Platform name"
          />
        </div>
        <button style={styles.iconBtn} onClick={() => onRemove(platform.id)} title="Remove"><Trash2 size={15} /></button>
      </div>
      <div style={{ ...styles.row3, ...(isMobile ? styles.stack : {}) }}>
        <Field label="Approx spend (AUD)">
          <DictationBox
            compact
            value={platform.approx_spend === "" || platform.approx_spend == null ? "" : String(platform.approx_spend)}
            onChange={(v) => onChange(platform.id, { approx_spend: v, spend_skipped: !String(v || "").trim() })}
            placeholder="e.g. 200"
            inputStyle={styles.input}
            ariaLabel="Approximate spend"
          />
        </Field>
        <Field label="Period">
          <select style={styles.input} value={platform.spend_period || "unknown"}
            onChange={(e) => onChange(platform.id, { spend_period: e.target.value })}>
            {SPEND_PERIODS.map((p) => <option key={p} value={p}>{p}</option>)}
          </select>
        </Field>
        <Field label="Users / seats">
          <input style={styles.input} type="number" min="0" inputMode="numeric" value={platform.users}
            onChange={(e) => onChange(platform.id, { users: e.target.value })} placeholder="Seats" />
        </Field>
      </div>
    </div>
  );
}

function WorkflowCard({ w, i, onChange, onRemove }) {
  const done = workflowComplete(w);
  return (
    <div style={{ ...styles.wfCard, ...(done ? styles.wfCardDone : {}) }}>
      <div style={styles.itemHead}>
        <span style={styles.itemNum}>{String(i + 1).padStart(2, "0")}</span>
        <input style={{ ...styles.input, flex: 1 }} value={w.goal_name}
          onChange={(e) => onChange(w.id, { goal_name: e.target.value, name: e.target.value })}
          onKeyDown={(e) => handleTypingKeyDown(e)}
          placeholder="Goal and name of the procedure" />
        <button style={styles.iconBtn} onClick={() => onRemove(w.id)} title="Remove"><Trash2 size={15} /></button>
      </div>
      <Field label="Who performs it (name and job title)">
        <DictationBox
          value={w.who}
          onChange={(v) => onChange(w.id, { who: v })}
          placeholder="Person name and job title"
          rows={2}
          inputStyle={styles.textarea}
          ariaLabel="Who performs this procedure"
        />
      </Field>
      <Field label="Current state (how it is done now)">
        <DictationBox
          value={w.process}
          onChange={(v) => onChange(w.id, { process: v, current: v })}
          placeholder="How this is done today"
          rows={3}
          inputStyle={styles.textarea}
          ariaLabel="Current state of the process"
        />
      </Field>
      <Field label="What's wrong">
        <DictationBox
          value={w.problems}
          onChange={(v) => onChange(w.id, { problems: v, comment: v })}
          placeholder="Where it breaks or wastes time"
          rows={3}
          inputStyle={styles.textarea}
          ariaLabel="What is wrong with the current setup"
        />
      </Field>
      <Field label="What DONE looks like">
        <DictationBox
          value={w.done}
          onChange={(v) => onChange(w.id, { done: v })}
          placeholder="How the flow should work once it is fixed"
          rows={3}
          inputStyle={styles.textarea}
          ariaLabel="What done looks like"
        />
      </Field>
    </div>
  );
}

function SubmitActions({ confirmed, setConfirmed, canSend, saveState, error, onBack, onSubmit, sticky }) {
  return (
    <div style={sticky ? styles.submitStickyInner : null}>
      <label style={{ ...styles.confirmRow, ...(sticky ? { margin: "0 0 12px" } : {}) }}>
        <input type="checkbox" checked={confirmed} onChange={(e) => setConfirmed(e.target.checked)} />
        <span>This is accurate. Submit sends the scope and shows your priced quote. The quote is valid for 14 days.</span>
      </label>
      {error && <div style={{ ...styles.errorBox, margin: "0 0 10px" }}>{error}</div>}
      <div style={styles.scopeActions}>
        {onBack && (
          <button type="button" className="ayg-btn" style={styles.ghostBtn} onClick={onBack}>Back to chat</button>
        )}
        <button
          type="button"
          className="ayg-btn"
          style={styles.primaryBtn}
          disabled={!confirmed || !canSend || saveState === "saving"}
          onClick={onSubmit}
        >
          {saveState === "saving" ? "Sending…" : "Submit"}
          {saveState !== "saving" && <ArrowRight size={15} style={{ marginLeft: 8 }} />}
        </button>
      </div>
      {(!confirmed || !canSend) && (
        <p style={styles.muteLine}>
          {!canSend
            ? "Add a business name, first name, last name, a valid email, phone, director details, at least one complete role, and at least one named workflow before submitting."
            : "Tick the box above, then Submit."}
        </p>
      )}
    </div>
  );
}

function ReviewScreen({ business, workflows, automations, notes, confirmed, setConfirmed, saveState, error, onBack, onSubmit, canSend, isMobile }) {
  return (
    <div style={{ ...styles.main, paddingBottom: 180, ...(isMobile ? { padding: "22px 14px 180px" } : {}) }}>
      <div style={styles.fade}>
        <SectionLabel icon={<FileText size={16} />} t="Review your scope" />
        <p style={styles.lead}>Read this through. Confirm it is accurate, then Submit at the bottom. You will see a priced quote you can Approve or Reject, and the same quote will be emailed as a branded link that expires in 14 days.</p>
        {!canSend && (
          <div style={styles.hintBox}>Add a business name, first name, last name, a valid email, phone, director details (and your job title if you are not the director), at least one complete role (job title, headcount, and a rough salary), and at least one named workflow before sending.</div>
        )}
        <div style={styles.card}>
          <div style={styles.scopeSecLabel}>Business</div>
          <ReviewRow k="Business" v={business.name} />
          <ReviewRow k="First name" v={business.first_name} />
          <ReviewRow k="Last name" v={business.last_name} />
          <ReviewRow k="Contact" v={(!business.first_name && !business.last_name) ? business.contactName : ""} />
          <ReviewRow k="Email" v={business.email} />
          <ReviewRow k="Phone" v={business.phone} />
          <ReviewRow k="Filler is director" v={business.filler_is_director === true ? "Yes" : business.filler_is_director === false ? "No" : ""} />
          <ReviewRow k="Your job title" v={business.filler_is_director === false ? business.job_title : ""} />
          <ReviewRow k="Director" v={[ (business.director || {}).first_name, (business.director || {}).last_name ].filter(Boolean).join(" ")} />
          <ReviewRow k="Director email" v={(business.director || {}).email} />
          <ReviewRow k="Director phone" v={(business.director || {}).phone} />
          <ReviewRow k="Industry" v={business.industry} />
          <ReviewRow k="Country" v={business.country} />
          <ReviewRow k="Build basis" v={business.product} />
        </div>
        <div style={styles.card}>
          <div style={styles.scopeSecLabel}>Org structure</div>
          {hasCompleteOrg(business) || (business.org_structure || []).some((r) => r.job_title) ? (
            (business.org_structure || []).filter((r) => r.job_title).map((r) => (
              <ReviewRow
                key={r.id}
                k={r.job_title}
                v={[
                  (r.headcount ? r.headcount + (Number(r.headcount) === 1 ? " person" : " people") : ""),
                  (r.approx_salary !== "" && r.approx_salary != null ? formatAud(parseRoughNumber(String(r.approx_salary), { money: true }) || r.approx_salary) + " " + (r.salary_period || "annual") : "salary not given"),
                ].filter(Boolean).join(" · ")}
              />
            ))
          ) : (
            <p style={styles.body}>No roles captured yet. Add at least one role with job title, headcount, and a rough salary. Sole trader is one row.</p>
          )}
          {!!orgHeadcount(business) && <ReviewRow k="Total headcount" v={String(orgHeadcount(business))} />}
        </div>
        <div style={styles.card}>
          <div style={styles.scopeSecLabel}>Platforms and spend</div>
          {business.platforms_skipped && !(business.platforms || []).some((p) => p.name) ? (
            <p style={styles.body}>{business.platforms_note || "No platforms captured."}</p>
          ) : (
            (business.platforms || []).filter((p) => p.name).map((p) => (
              <ReviewRow
                key={p.id}
                k={p.name}
                v={[
                  (p.approx_spend !== "" && p.approx_spend != null ? formatAud(parseRoughNumber(String(p.approx_spend)) || p.approx_spend) + (p.spend_period && p.spend_period !== "unknown" ? " " + p.spend_period : "") : "spend not given"),
                  (p.users !== "" && p.users != null ? p.users + " users" : "users not given"),
                ].join(" · ")}
              />
            ))
          )}
        </div>
        <div style={styles.card}>
          <div style={styles.scopeSecLabel}>Workflows</div>
          <p style={styles.body}>{WORKFLOW_WHAT}</p>
          <p style={{ ...styles.body, marginTop: 8 }}>{WORKFLOW_EXAMPLE}</p>
        </div>
        {workflows.map((w, i) => (
          <div key={w.id} style={styles.card}>
            <div style={styles.scopeSecLabel}>Workflow {String(i + 1).padStart(2, "0")}</div>
            <ReviewRow k="Goal and name" v={w.goal_name || w.name} />
            <ReviewRow k="Who" v={w.who} />
            <ReviewRow k="Current state" v={w.process || w.current} />
            <ReviewRow k="Problems" v={w.problems} />
            <ReviewRow k="Done" v={w.done} />
          </div>
        ))}
        {automations.length > 0 && (
          <div style={styles.card}>
            <div style={styles.scopeSecLabel}>Automations mentioned</div>
            {automations.map((a, i) => (
              <p key={i} style={styles.bullet}>WHEN {a.trigger || "an event"}, THEN {a.action || a.detail || "an action"}.</p>
            ))}
          </div>
        )}
        {notes && (
          <div style={styles.card}>
            <div style={styles.scopeSecLabel}>Notes</div>
            <p style={styles.body}>{notes}</p>
          </div>
        )}
        <div style={styles.card}>
          <div style={styles.scopeSecLabel}>Confirm and submit</div>
          <SubmitActions
            confirmed={confirmed}
            setConfirmed={setConfirmed}
            canSend={canSend}
            saveState={saveState}
            error={error}
            onBack={onBack}
            onSubmit={onSubmit}
          />
        </div>
      </div>
      <div style={styles.submitSticky} role="region" aria-label="Submit scope">
        <SubmitActions
          confirmed={confirmed}
          setConfirmed={setConfirmed}
          canSend={canSend}
          saveState={saveState}
          error={null}
          onBack={onBack}
          onSubmit={onSubmit}
          sticky
        />
      </div>
    </div>
  );
}

function InstantQuoteCard({ quote, decision, deciding, rejectReason, onReason, onAccept, onReject }) {
  const expired = decision === "expired" || (quote.expires_at && Date.parse(quote.expires_at) < Date.now());
  const done = decision === "accepted" || decision === "rejected";
  return (
    <div style={{ ...styles.card, borderColor: C.accent, marginBottom: 18 }}>
      <div style={styles.scopeSecLabel}>Your quote {quote.quote_ref}</div>
      {expired ? (
        <p style={styles.body}>This quote has expired. Prices and Approve / Reject are no longer available.</p>
      ) : (
        <>
          {(quote.items || []).map((it) => (
            <div key={it.id} style={{ display: "flex", justifyContent: "space-between", gap: 12, padding: "8px 0", borderBottom: "1px solid " + C.line, fontSize: 14 }}>
              <span>{it.name}</span>
              <strong style={{ whiteSpace: "nowrap" }}>{moneyFmt(it.line_total)} ex GST</strong>
            </div>
          ))}
          <div style={{ marginTop: 12, fontSize: 15, lineHeight: 1.6 }}>
            <div>Total ex GST <strong style={{ float: "right" }}>{moneyFmt(quote.subtotal)}</strong></div>
            <div style={{ color: C.grey }}>GST 10% <strong style={{ float: "right", fontWeight: 500 }}>{moneyFmt(quote.gst)}</strong></div>
            <div style={{ marginTop: 8, fontFamily: FONT, fontSize: 22 }}>Total inc GST <strong style={{ float: "right", color: C.accent }}>{moneyFmt(quote.total_inc_gst)}</strong></div>
          </div>
        </>
      )}
      {!expired && (
        <p style={{ color: C.grey, fontSize: 13, margin: "14px 0 0", lineHeight: 1.5 }}>
          Valid until {fmtAuDate(quote.expires_at)}. Delivery {quote.delivery_days || 28} days from approval.
          Check your email for the branded quote link. That link expires in 14 days.
        </p>
      )}
      {quote.view_path && (
        <p style={{ margin: "10px 0 0" }}>
          <a href={quote.view_path} style={{ color: C.accent, fontWeight: 600 }}>Open the branded quote page</a>
        </p>
      )}
      {expired && <div style={{ ...styles.errorBox, margin: "14px 0 0" }}>This quote has expired. Approve and Reject are no longer available.</div>}
      {decision === "accepted" && <div style={{ ...styles.hintBox, marginTop: 14 }}>Thank you. This quote is approved. Gus has been notified.</div>}
      {decision === "rejected" && <div style={{ ...styles.hintBox, marginTop: 14 }}>This quote has been rejected. Gus has been notified.</div>}
      {decision === "error" && <div style={{ ...styles.errorBox, margin: "14px 0 0" }}>Could not save your decision. Try the branded quote page or try again.</div>}
      {!done && !expired && (
        <div style={{ marginTop: 16 }}>
          <button className="ayg-btn" style={{ ...styles.primaryBtn, marginRight: 8 }} disabled={deciding} onClick={onAccept}>
            {deciding ? "Saving..." : "Approve this quote"}
          </button>
          <div style={{ marginTop: 12 }}>
            <textarea style={styles.textarea} value={rejectReason} onChange={(e) => onReason(e.target.value)} placeholder="Optional reason if you reject" />
            <button className="ayg-btn" style={{ ...styles.ghostBtn, marginTop: 8 }} disabled={deciding} onClick={onReject}>Reject this quote</button>
          </div>
        </div>
      )}
    </div>
  );
}

function ThanksScreen({ quoteRef, quote, business, decision, deciding, rejectReason, onReason, onAccept, onReject, onReset }) {
  const priced = quote && quote.quote_ref;
  return (
    <div style={styles.main}>
      <div style={{ ...styles.card, animation: "slideUp .4s ease" }}>
        <div style={styles.badge}>{priced ? "Quote ready" : "Sent for review"}</div>
        <h2 style={{ ...styles.secH, margin: "12px 0 10px" }}>Thanks{contactDisplayName(business) ? ", " + (business.first_name || contactDisplayName(business).split(" ")[0]) : ""}.</h2>
        <p style={styles.body}>
          {priced
            ? "Your priced quote is below. Approve or reject it here. The same quote is in your email as a branded link that expires in 14 days."
            : "Your scope is with Gus for internal review. A quote will be prepared and sent after he has approved and priced it. You will not see a price until then."}
        </p>
        {priced ? (
          <InstantQuoteCard
            quote={quote}
            decision={decision}
            deciding={deciding}
            rejectReason={rejectReason}
            onReason={onReason}
            onAccept={onAccept}
            onReject={onReject}
          />
        ) : quoteRef ? (
          <div style={styles.refBox}>
            <div style={styles.scopeSecLabel}>Reference</div>
            <div style={styles.refVal}>{quoteRef}</div>
            <div style={styles.muteLine}>Draft on our side. Not a priced quote.</div>
          </div>
        ) : null}
        <div style={{ marginTop: 22 }}>
          <div style={styles.scopeSecLabel}>What happens next</div>
          {priced ? (
            <>
              <p style={styles.bullet}>Approve or reject this quote on this page or via the email link.</p>
              <p style={styles.bullet}>The link expires in 14 days. After that, prices and Approve / Reject are hidden.</p>
              <p style={styles.bullet}>Typical custom delivery is 28 days from approval.</p>
            </>
          ) : (
            <>
              <p style={styles.bullet}>Gus reads the workflows and prices the work internally.</p>
              <p style={styles.bullet}>You receive a quote once he has approved it. That quote is typically valid for 14 days.</p>
              <p style={styles.bullet}>Typical custom delivery is about 28 days, sometimes sooner.</p>
            </>
          )}
        </div>
        <p style={styles.fineprint}>Keep the reference handy if you follow up. If you would rather talk it through, book a call at askyrgrandpa.com/book-a-meeting/</p>
        <button className="ayg-btn" style={{ ...styles.ghostBtn, marginTop: 16 }} onClick={onReset}>Start another scope</button>
      </div>
    </div>
  );
}

function SectionLabel({ icon, t }) {
  return (
    <div style={styles.secTitle}>
      <span style={styles.secIcon}>{icon}</span>
      <h2 style={styles.secH}>{t}</h2>
    </div>
  );
}
function Field({ label, children, required }) {
  return (
    <div style={{ marginBottom: 14 }}>
      <label style={styles.label}>{label}{required && <span style={styles.req}> *</span>}</label>
      {children}
    </div>
  );
}
function ReviewRow({ k, v }) {
  if (!String(v || "").trim()) return null;
  return (
    <div style={styles.reviewRow}>
      <div style={styles.reviewK}>{k}</div>
      <div style={styles.reviewV}>{v}</div>
    </div>
  );
}

const styles = {
  app: { minHeight: "100vh", background: C.paper, fontFamily: FONT, color: C.ink, paddingBottom: 48 },
  header: { borderBottom: "1px solid " + C.line, padding: "28px 24px 20px", textAlign: "center", background: C.surface },
  badge: { display: "inline-block", background: C.accent, color: "#fff", fontSize: 10, fontFamily: FONT, fontWeight: 700, letterSpacing: 2.2, padding: "5px 13px", marginBottom: 14, textTransform: "uppercase", borderRadius: 999 },
  h1: { color: C.ink, fontFamily: FONT, fontSize: "clamp(28px,5.5vw,48px)", fontWeight: 800, letterSpacing: -1.4, margin: "0 0 8px", lineHeight: 1.02 },
  h1em: { fontStyle: "italic", fontWeight: 800, color: C.accent },
  subtitle: { color: C.grey, fontSize: 14, fontFamily: FONT, fontWeight: 500, letterSpacing: 0.1, margin: 0 },
  progressTrack: { height: 3, width: "100%", background: C.line },
  progressFill: { height: "100%", background: C.accent, transition: "width .4s ease" },
  shell: { display: "grid", gridTemplateColumns: "minmax(0,1.05fr) minmax(280px,0.95fr)", gap: 20, maxWidth: 1180, margin: "0 auto", padding: "24px 20px 20px", alignItems: "start" },
  shellMobile: { gridTemplateColumns: "1fr", padding: "16px 14px 24px" },
  chatCol: { background: C.surface, border: "1px solid " + C.line, borderRadius: 18, display: "flex", flexDirection: "column", minHeight: 560, maxHeight: "calc(100vh - 180px)", boxShadow: "0 18px 50px rgba(17,17,16,.08)", overflow: "hidden" },
  chatHead: { display: "flex", justifyContent: "space-between", alignItems: "center", gap: 12, padding: "14px 16px", background: C.ink, color: "#fff" },
  chatName: { fontFamily: FONT, fontWeight: 700, fontSize: 16, letterSpacing: "-0.02em", color: "#fff" },
  chatRole: { color: "#b9b5b0", fontSize: 12, marginTop: 2, fontWeight: 500 },
  reviewJump: { background: C.accent, color: "#fff", border: "none", borderRadius: 8, padding: "8px 12px", fontSize: 12, fontFamily: FONT, fontWeight: 700, cursor: "pointer" },
  log: { flex: 1, overflowY: "auto", padding: "16px 16px 8px", display: "flex", flexDirection: "column", gap: 10, background: C.paper },
  bubbleBot: { alignSelf: "flex-start", maxWidth: "92%", background: C.surface, color: C.ink, border: "1px solid " + C.line, borderRadius: "16px 16px 16px 5px", padding: "11px 14px", fontSize: 14.5, lineHeight: 1.55, whiteSpace: "pre-wrap" },
  bubbleYou: { alignSelf: "flex-end", maxWidth: "92%", background: C.ink, color: "#fff", borderRadius: "16px 16px 5px 16px", padding: "11px 14px", fontSize: 14.5, lineHeight: 1.55, whiteSpace: "pre-wrap" },
  typing: { color: C.grey, fontSize: 12, fontWeight: 700, letterSpacing: 1.2, textTransform: "uppercase", animation: "pulse 1.5s ease-in-out infinite" },
  composer: { display: "grid", gridTemplateColumns: "minmax(0,1fr) auto", gap: 8, padding: "12px 14px", borderTop: "1px solid " + C.line, alignItems: "flex-end", background: C.surface },
  composerMobile: { paddingBottom: "max(12px, env(safe-area-inset-bottom))" },
  composerInput: { width: "100%", background: C.surface, border: "1px solid " + C.line, borderRadius: 12, color: C.ink, fontSize: 15, fontFamily: FONT, padding: "10px 12px", outline: "none", resize: "vertical", minHeight: 52, lineHeight: 1.45, boxSizing: "border-box" },
  dictateRow: { display: "grid", gridTemplateColumns: "minmax(0,1fr) 44px", gap: 8, alignItems: "flex-end" },
  micBtn: { width: 44, height: 44, flexShrink: 0, display: "inline-flex", alignItems: "center", justifyContent: "center", background: C.surface, color: C.ink, border: "1px solid " + C.line, borderRadius: 12, cursor: "pointer" },
  micBtnOn: { background: C.accent, color: "#fff", borderColor: C.accent },
  micBtnOff: { opacity: 0.45, cursor: "not-allowed" },
  sendBtn: { display: "inline-flex", alignItems: "center", justifyContent: "center", background: C.accent, color: "#fff", border: "none", borderRadius: 12, padding: "12px 16px", fontSize: 13, fontFamily: FONT, fontWeight: 700, cursor: "pointer", height: 44, minWidth: 72, whiteSpace: "nowrap" },
  hint: { color: C.grey, fontSize: 11.5, margin: "0 16px 12px", lineHeight: 1.4 },
  listenHint: { color: C.accentDeep, fontSize: 12, fontWeight: 700, margin: "6px 0 0", letterSpacing: 0.4 },
  cardCol: { minWidth: 0 },
  cardHead: { display: "flex", alignItems: "baseline", gap: 10, marginBottom: 4 },
  cardTitle: { fontFamily: FONT, fontSize: 24, fontWeight: 800, margin: 0, letterSpacing: -0.6 },
  cardLead: { color: C.grey, fontSize: 13.5, margin: "0 0 14px", lineHeight: 1.5 },
  main: { maxWidth: 760, margin: "0 auto", padding: "32px 20px 40px" },
  fade: { animation: "slideUp .35s ease" },
  secTitle: { display: "flex", alignItems: "center", gap: 10, marginBottom: 12 },
  secK: { fontFamily: FONT, fontWeight: 700, color: C.accent, fontSize: 11, letterSpacing: 1.6, textTransform: "uppercase" },
  secIcon: { color: C.accent, display: "flex" },
  secH: { color: C.ink, fontFamily: FONT, fontSize: 22, fontWeight: 800, margin: 0, letterSpacing: -0.4 },
  lead: { color: C.grey, fontSize: 15, margin: "0 0 22px", lineHeight: 1.6 },
  card: { background: C.surface, border: "1px solid " + C.line, borderRadius: 12, padding: 20, marginBottom: 14, boxShadow: "0 1px 2px rgba(17,17,16,0.04)" },
  wfCard: { background: C.paper, border: "1px solid " + C.line, borderRadius: 12, padding: 14, marginBottom: 12 },
  wfCardDone: { borderColor: C.success },
  label: { display: "block", color: C.grey, fontSize: 10.5, fontFamily: FONT, fontWeight: 600, letterSpacing: 1.5, textTransform: "uppercase", marginBottom: 8 },
  input: { width: "100%", background: C.surface, border: "1px solid " + C.lineSoft, borderRadius: 8, color: C.ink, fontSize: 15, fontFamily: FONT, padding: "11px 13px", outline: "none", boxSizing: "border-box", transition: "border-color .2s" },
  textarea: { width: "100%", background: C.surface, border: "1px solid " + C.lineSoft, borderRadius: 8, color: C.ink, fontSize: 15, fontFamily: FONT, padding: "11px 13px", outline: "none", boxSizing: "border-box", resize: "vertical", minHeight: 72, lineHeight: 1.5 },
  row2: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 },
  row3: { display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 12 },
  stack: { gridTemplateColumns: "1fr" },
  req: { color: C.accent, fontWeight: 700 },
  chipWrap: { display: "flex", flexWrap: "wrap", gap: 8 },
  chip: { display: "flex", alignItems: "center", background: C.surface, border: "1px solid " + C.line, borderRadius: 999, color: C.grey, fontSize: 13, fontFamily: FONT, fontWeight: 600, padding: "8px 14px", cursor: "pointer" },
  chipOn: { background: C.accentSoft, borderColor: C.accent, color: C.accentDeep },
  empty: { background: C.accentSoft, border: "1px dashed " + C.accentTint, borderRadius: 12, padding: "18px 14px", textAlign: "center", color: C.grey, fontSize: 13.5, marginBottom: 12, lineHeight: 1.5 },
  itemHead: { display: "flex", alignItems: "center", gap: 10, marginBottom: 12 },
  itemNum: { fontFamily: FONT, fontWeight: 700, color: C.accent, fontSize: 14, letterSpacing: 0.5 },
  iconBtn: { background: "transparent", border: "1px solid " + C.line, borderRadius: 8, color: C.grey, padding: 7, cursor: "pointer", display: "flex" },
  addBtn: { display: "inline-flex", alignItems: "center", background: "transparent", color: C.accentDeep, border: "1px dashed " + C.accent, borderRadius: 999, padding: "10px 16px", fontSize: 13, fontFamily: FONT, fontWeight: 700, letterSpacing: 0.2, cursor: "pointer" },
  hintBox: { display: "flex", flexWrap: "wrap", alignItems: "center", gap: 2, background: C.accentSoft, border: "1px solid " + C.accentTint, borderRadius: 12, color: C.ink, fontSize: 13.5, padding: "12px 16px", marginBottom: 16, lineHeight: 1.5 },
  errorBox: { background: C.accentSoft, border: "1px solid " + C.accentTint, borderRadius: 12, color: C.ink, fontSize: 13.5, padding: 12, margin: "0 14px 10px", lineHeight: 1.5 },
  body: { color: C.ink, fontSize: 15, lineHeight: 1.7, margin: 0 },
  bullet: { color: C.ink, fontSize: 14.5, lineHeight: 1.6, margin: "0 0 8px", paddingLeft: 14, borderLeft: "2px solid " + C.accentTint },
  scopeSecLabel: { color: C.accent, fontSize: 10.5, fontFamily: FONT, fontWeight: 700, letterSpacing: 1.5, textTransform: "uppercase", marginBottom: 10 },
  scopeActions: { display: "flex", flexWrap: "wrap", gap: 8, marginTop: 8 },
  primaryBtn: { display: "inline-flex", alignItems: "center", justifyContent: "center", background: C.accent, color: "#fff", border: "none", borderRadius: 8, padding: "13px 22px", fontSize: 14, fontFamily: FONT, fontWeight: 700, letterSpacing: 0.1, cursor: "pointer", boxShadow: "0 8px 20px rgba(224,94,31,0.22)" },
  ghostBtn: { display: "inline-flex", alignItems: "center", background: "transparent", color: C.ink, border: "1px solid " + C.line, borderRadius: 8, padding: "13px 18px", fontSize: 14, fontFamily: FONT, fontWeight: 600, cursor: "pointer" },
  confirmRow: { display: "flex", gap: 10, alignItems: "flex-start", fontSize: 14, lineHeight: 1.5, color: C.ink, margin: "8px 0 16px", cursor: "pointer" },
  submitSticky: {
    position: "fixed", left: 0, right: 0, bottom: 0, zIndex: 40,
    background: C.surface, borderTop: "1px solid " + C.line,
    boxShadow: "0 -12px 32px rgba(17,17,16,0.08)",
    padding: "14px 20px max(14px, env(safe-area-inset-bottom))",
  },
  submitStickyInner: { maxWidth: 760, margin: "0 auto" },
  chatSubmitSticky: {
    position: "sticky", bottom: 0, zIndex: 30,
    display: "flex", flexWrap: "wrap", alignItems: "center", justifyContent: "space-between", gap: 12,
    background: C.surface, border: "1px solid " + C.line, borderRadius: 12,
    padding: "12px 16px", margin: "0 20px 16px",
    boxShadow: "0 8px 24px rgba(17,17,16,0.08)",
  },
  reviewRow: { display: "grid", gridTemplateColumns: "140px 1fr", gap: 10, marginBottom: 8, fontSize: 14.5 },
  reviewK: { color: C.grey, fontSize: 11, letterSpacing: 1.1, textTransform: "uppercase", paddingTop: 3, fontWeight: 600 },
  reviewV: { color: C.ink, lineHeight: 1.5, whiteSpace: "pre-wrap" },
  refBox: { marginTop: 18, background: C.accentSoft, border: "1px solid " + C.accentTint, borderRadius: 12, padding: 16 },
  refVal: { fontFamily: FONT, fontSize: 26, fontWeight: 800, letterSpacing: -0.5 },
  muteLine: { color: C.grey, fontSize: 12.5, marginTop: 4 },
  fineprint: { color: C.grey, fontSize: 12, fontFamily: FONT, marginTop: 18, lineHeight: 1.5 },
};

ReactDOM.createRoot(document.getElementById("root")).render(
  React.createElement(ScopeBuilder),
);
