// ============================================================
// QBE Agentic Experience — chat panel (left) + stage view (right)
//
// 11-stage flow: INTRO → PRODUCT_TYPE → VEHICLE → VEHICLE_CONFIRM
// → GARAGE → USAGE → MAIN_DRIVER → OTHER_DRIVERS → QUOTE
// → CHECKOUT → CONFIRMATION.
//
// The split-panel layout stays in place at every stage. ChatPanel is
// populated at every stage (postAgent pushes the next message) and the
// right-pane view always has a component (renderView returns one per
// stage). Users advance by tapping chat chips OR interacting with the
// right-pane view — the two panels are synchronised via the "stage"
// state held here.
// ============================================================

function AgenticExperience({ initialMessage, onExit }) {
  // Conversation transcript shown in the chat panel.
  // Messages: { id, role: 'agent'|'user', content, chips?, widget?, tool? }
  const [messages, setMessages] = React.useState([]);
  const [isTyping, setIsTyping] = React.useState(false);

  // Stage state machine — user arrived from "Get a car insurance quote"
  // so we skip INTRO and seed at PRODUCT_TYPE.
  const [stage, setStage] = React.useState('PRODUCT_TYPE');

  // Answers collected through the flow. Shape matches the spec.
  const [answers, setAnswers] = React.useState({
    product:        'Comprehensive',     // pre-selected for the demo
    vehicle:        null,                // { rego, state, lookup }
    vehicleChecks:  { registered: 'yes', damage: 'no', recentlyBought: 'no', kmsPerYear: '10,000 – 20,000 KMs' },
    garage:         null,                // { line, suburb, state, postcode }
    usage:          { primary: 'Private use', finance: 'No Finance' },
    mainDriver:     null,                // { dob, gender, licenceYears, history: [] }
    otherDrivers:   { mode: null, email: '' },  // mode: 'just-me' | 'add-another'
    quote:          { ...DEFAULT_QUOTE },
    // Sub-state machine for the "outside my budget" negotiation at QUOTE:
    //   idle           — no budget ask in flight; respond to triggers
    //   awaitingBudget — agent has asked for a dollar amount
    //   budgetApplied  — adjustment applied; revert on manual edit
    quoteSubStage:  'idle',
    targetBudget:   null,                // number $/mo when budgetApplied
  });

  // Profile shown at the top of the chat panel.
  const customerProfile = { status: 'Online · here to help' };

  // Live price derived from the quote state; used by the live-price banner
  // from USAGE onwards and by QuoteView's headline.
  const livePrice = React.useMemo(() => computePrice(answers.quote), [answers.quote]);

  // ---------- chat helpers ----------
  const postAgent = React.useCallback((content, opts = {}) => {
    setIsTyping(true);
    const delay = opts.delay ?? 650;
    setTimeout(() => {
      setIsTyping(false);
      setMessages(prev => [...prev, {
        id: `m-agent-${prev.length}`,
        role: 'agent',
        content,
        chips:  opts.chips  || null,
        widget: opts.widget || null,
        tool:   opts.tool   || null,
      }]);
    }, delay);
  }, []);

  const postUser = React.useCallback((content) => {
    setMessages(prev => [...prev, {
      id: `m-user-${prev.length}`,
      role: 'user',
      content,
    }]);
  }, []);

  // Push the canonical agent message for a given stage.
  const postStageMessage = React.useCallback((nextStage, opts = {}) => {
    const msg = STAGE_MESSAGES[nextStage];
    if (!msg) return;
    postAgent(msg.content, {
      chips:  msg.chips  || null,
      widget: msg.widget || null,
      delay:  opts.delay,
    });
  }, [postAgent]);

  // Transition to a new stage, merging any answer patch and posting that
  // stage's agent message.
  const advanceTo = React.useCallback((nextStage, patch = {}, opts = {}) => {
    if (patch && Object.keys(patch).length) setAnswers(prev => ({ ...prev, ...patch }));
    setStage(nextStage);
    if (!opts.silent) postStageMessage(nextStage, opts);
  }, [postStageMessage]);

  // ---------- bootstrap ----------
  // Seed the conversation on mount: optional user-message echo from the
  // CTA click, then the PRODUCT_TYPE agent message.
  const bootstrappedRef = React.useRef(false);
  React.useEffect(() => {
    if (bootstrappedRef.current) return;
    bootstrappedRef.current = true;
    if (initialMessage) postUser(initialMessage);
    postStageMessage('PRODUCT_TYPE', { delay: initialMessage ? 500 : 200 });
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  // ---------- handlers ----------
  // Main state-machine dispatcher. A user tapping a chip, submitting a
  // chat widget, or clicking a right-pane primary action all route here.
  // Sentinels starting with "__" are internal transitions (no user bubble).
  const handleAnswer = (value, opts = {}) => {
    const isSentinel = typeof value === 'string' && value.startsWith('__');
    // Only echo string user actions into the chat. Object payloads are
    // state patches from right-pane edits (field changes, selections,
    // slider/tier updates) — they should update answers silently.
    if (!isSentinel && !opts.silent && typeof value === 'string' && value.trim()) {
      postUser(value);
    }

    switch (stage) {
      case 'PRODUCT_TYPE': {
        // Any product pick drives us into the VEHICLE lookup. For the
        // demo we stay on car-insurance pricing regardless of product.
        const product = typeof value === 'string' ? value : 'Comprehensive';
        setAnswers(prev => ({
          ...prev,
          product,
          // Stay on Comprehensive in the live-price math unless user
          // explicitly picked Third Party Property.
          quote: {
            ...prev.quote,
            tier: product === 'Third Party Property' ? 'ThirdPartyProperty' : 'Comprehensive',
          },
        }));
        advanceTo('VEHICLE');
        break;
      }

      case 'VEHICLE': {
        // Called with the rego-state widget payload: { rego, state }.
        const payload = typeof value === 'object' ? value : {};
        const rego = payload.rego || 'BXA58T';
        const state = payload.state || 'NSW';
        postUser(`${rego} · ${state}`);
        setAnswers(prev => ({ ...prev, vehicle: { rego, state, lookup: null } }));

        // Tool-call indicator in chat — pulsing dot message that auto
        // clears when the lookup "completes" and we advance.
        postAgent('Connecting to NEVDIS vehicle registry…', { tool: 'nevdis', delay: 350 });

        setTimeout(() => {
          setAnswers(prev => ({
            ...prev,
            vehicle: { ...(prev.vehicle || {}), lookup: VEHICLE_LOOKUP_RESULT },
          }));
          advanceTo('VEHICLE_CONFIRM');
        }, 1550);
        break;
      }

      case 'VEHICLE_CONFIRM': {
        // Value is the vehicleChecks patch from the right-pane Next button.
        if (value && typeof value === 'object') {
          setAnswers(prev => ({ ...prev, vehicleChecks: { ...prev.vehicleChecks, ...value } }));
        }
        advanceTo('GARAGE');
        break;
      }

      case 'GARAGE': {
        // Address submission — either from the chat widget or the right-pane
        // input. Value is a string OR an address object. The string is
        // already echoed to chat by the generic guard above; here we just
        // resolve and store the address.
        const address = typeof value === 'string'
          ? { ...GARAGE_MOCK_ADDRESS, line: value }
          : (value || GARAGE_MOCK_ADDRESS);
        setAnswers(prev => ({ ...prev, garage: address }));
        advanceTo('USAGE');
        break;
      }

      case 'USAGE': {
        // Value is the usage patch from right-pane selections, or
        // '__next__' when the Continue button is tapped.
        if (value && typeof value === 'object') {
          setAnswers(prev => ({ ...prev, usage: { ...prev.usage, ...value } }));
          return; // live update only, don't transition
        }
        advanceTo('MAIN_DRIVER');
        break;
      }

      case 'MAIN_DRIVER': {
        // Value is either a driver profile patch (from live field edits)
        // or the 'None of the above' CTA / sentinel advance.
        if (value && typeof value === 'object') {
          setAnswers(prev => ({ ...prev, mainDriver: { ...(prev.mainDriver || {}), ...value } }));
          return;
        }
        // Default the main driver for the happy path if the user skipped
        // filling fields but tapped "None of the above".
        setAnswers(prev => ({
          ...prev,
          mainDriver: prev.mainDriver || {
            dob: '12/06/1990', gender: 'Prefer not to say', licenceYears: '5+ years', history: [],
          },
        }));
        advanceTo('OTHER_DRIVERS');
        break;
      }

      case 'OTHER_DRIVERS': {
        // Chip "Just me" | "Add another driver", or email patch {email}.
        if (value && typeof value === 'object' && 'email' in value) {
          setAnswers(prev => ({ ...prev, otherDrivers: { ...prev.otherDrivers, email: value.email } }));
          return;
        }
        const mode = value === 'Add another driver' ? 'add-another' : 'just-me';
        setAnswers(prev => ({ ...prev, otherDrivers: { ...prev.otherDrivers, mode } }));
        advanceTo('QUOTE');
        break;
      }

      case 'QUOTE': {
        // --- Live right-pane edits (object patches) ---
        if (value && typeof value === 'object') {
          setAnswers(prev => {
            const hadBudget = prev.quoteSubStage === 'budgetApplied';
            const touchesCoverage = ['tier', 'excess', 'sumInsured', 'extras', 'frequency']
              .some(k => k in value);
            // Manual edit after a budget match clears the adjustment
            // silently — the callout disappearing is feedback enough.
            const clearBudget = hadBudget && touchesCoverage;
            return {
              ...prev,
              quote: {
                ...prev.quote,
                ...value,
                ...(clearBudget ? { budgetDiscount: 0 } : null),
              },
              ...(clearBudget ? { quoteSubStage: 'idle', targetBudget: null } : null),
            };
          });
          return;
        }

        // --- Continue sentinel only advances to CHECKOUT ---
        if (value === '__checkout__') {
          advanceTo('CHECKOUT');
          break;
        }

        // --- Everything else is conversational. Route by sub-stage. ---
        const text = typeof value === 'string' ? value.trim() : '';
        const numberMatch = text.match(/\$?\s*(\d+(?:\.\d+)?)/);
        const hasNumber = !!numberMatch;
        const triggerRe = /budget|expensive|afford|cheap(er)?|lower|reduce|price/i;
        const isTrigger = triggerRe.test(text) || text === 'Outside my budget' || hasNumber;

        if (answers.quoteSubStage === 'idle' || answers.quoteSubStage === 'budgetApplied') {
          if (isTrigger) {
            // Synchronously enter awaitingBudget — must NOT live inside a
            // setTimeout, or a rapid follow-up message would land in the
            // wrong sub-stage branch on the next render.
            setAnswers(prev => ({
              ...prev,
              quoteSubStage: 'awaitingBudget',
              // Clear any previous budget adjustment so the next pick is fresh.
              quote: { ...prev.quote, budgetDiscount: 0 },
              targetBudget: null,
            }));
            postAgent("What\u2019s the most you\u2019d like to pay per month?", { chips: ['Cancel'] });
          } else {
            // Free-text with no budget signal — acknowledge, don't advance.
            postAgent('Got it. Use the options on the right to keep going, or ask me about your budget.', { delay: 400 });
          }
          break;
        }

        if (answers.quoteSubStage === 'awaitingBudget') {
          // Cancel path
          if (/^cancel$/i.test(text) || text === 'Cancel') {
            const currentPrice = computePrice(answers.quote).monthly;
            setAnswers(prev => ({ ...prev, quoteSubStage: 'idle' }));
            postAgent(`No problem, your quote stays at $${currentPrice.toFixed(2)}/mo. Let me know if you want to revisit.`);
            break;
          }

          if (!hasNumber) {
            postAgent("I didn\u2019t catch a dollar amount. What\u2019s your monthly budget?", { chips: ['Cancel'] });
            break;
          }

          const target = parseFloat(numberMatch[1]);

          // Range guards — keep the planner sane and avoid desync with the
          // Math.max(24, …) floor inside computePrice.
          if (target < 24) {
            postAgent("That\u2019s below our minimum premium. What\u2019s your monthly budget?", { chips: ['Cancel'] });
            break;
          }
          if (target > 500) {
            postAgent("That seems out of range for a monthly premium. What\u2019s a realistic budget?", { chips: ['Cancel'] });
            break;
          }

          const plan = planBudgetAdjustment(answers.quote, target);

          if (plan.kind === 'already-under') {
            setAnswers(prev => ({ ...prev, quoteSubStage: 'idle' }));
            postAgent(`You\u2019re already under that — your current premium is $${plan.currentPrice.toFixed(2)}/mo. Want to adjust anything else?`);
            break;
          }

          // Apply the adjustment + enter budgetApplied synchronously, so a
          // follow-up user message reads the new sub-stage on next render.
          setAnswers(prev => ({
            ...prev,
            quoteSubStage: 'budgetApplied',
            targetBudget:  target,
            quote: {
              ...prev.quote,
              excess:         plan.excess,
              budgetDiscount: plan.budgetDiscount,
            },
          }));

          const priceTxt = `$${target.toFixed(2)}/mo`;
          const discountTxt = `$${plan.budgetDiscount.toFixed(2)}`;
          if (plan.kind === 'excess-plus-discount') {
            const prevExcess = answers.quote.excess;
            postAgent(
              `I\u2019ve bumped your excess from $${prevExcess.toLocaleString()} to $${plan.excess.toLocaleString()} and applied a ${discountTxt} budget-friendly discount. Your new premium is ${priceTxt} — let me know if you want to tweak anything else.`
            );
          } else {
            postAgent(
              `Applied a ${discountTxt} budget-friendly discount to hit your ${priceTxt} budget. Excess stays at $${plan.excess.toLocaleString()}.`
            );
          }
          break;
        }

        // Fallback (shouldn't reach here) — treat like idle free-text.
        postAgent('Got it. Use the options on the right to keep going.', { delay: 400 });
        break;
      }

      case 'CHECKOUT': {
        advanceTo('CONFIRMATION');
        break;
      }

      default: {
        // Free-text while no chips / widget are active — acknowledge
        // without advancing.
        postAgent('Got it. Use the options on the right to keep going.', { delay: 400 });
      }
    }
  };

  // ---------- view rendering ----------
  const progressIndex = STAGE_TO_PROGRESS[stage];
  const showLivePrice = STAGES_WITH_LIVE_PRICE.has(stage);

  const renderView = () => {
    const kind = STAGE_VIEW[stage];
    switch (kind) {
      case 'ProductType':
        return <ProductTypeView
                 selected={answers.product}
                 onAction={handleAnswer}
               />;

      case 'VehicleLookup':
        return <VehicleLookupView lookup={answers.vehicle?.lookup} />;

      case 'VehicleConfirm':
        return <VehicleConfirmView
                 lookup={answers.vehicle?.lookup || VEHICLE_LOOKUP_RESULT}
                 checks={answers.vehicleChecks}
                 onNext={(patch) => handleAnswer(patch)}
               />;

      case 'Garage':
        return <GarageView
                 garage={answers.garage}
                 onSubmit={(address) => handleAnswer(address)}
               />;

      case 'Usage':
        return <UsageView
                 value={answers.usage}
                 onChange={(patch) => handleAnswer(patch)}
                 onContinue={() => handleAnswer('__next__', { silent: true })}
               />;

      case 'MainDriver':
        return <MainDriverView
                 value={answers.mainDriver}
                 onChange={(patch) => handleAnswer(patch)}
                 onCleanRecord={() => handleAnswer('None of the above')}
               />;

      case 'OtherDrivers':
        return <OtherDriversView
                 mainDriver={answers.mainDriver}
                 value={answers.otherDrivers}
                 onChangeEmail={(email) => handleAnswer({ email })}
               />;

      case 'Quote':
        return <QuoteView
                 quote={answers.quote}
                 price={livePrice}
                 targetBudget={answers.targetBudget}
                 onChange={(patch) => handleAnswer(patch)}
                 onContinue={() => handleAnswer('__checkout__', { silent: true })}
               />;

      case 'Checkout':
        return <CheckoutView
                 answers={answers}
                 price={livePrice}
                 onSubmit={() => handleAnswer('__submit__', { silent: true })}
               />;

      case 'Confirmation':
        return <ConfirmationView
                 answers={answers}
                 price={livePrice}
               />;

      default:
        return null;
    }
  };

  // Suburb label for the live-price banner.
  const priceSuburb = answers.garage?.suburb || GARAGE_MOCK_ADDRESS.suburb;

  return (
    <div className="agentic-root">
      <div className="agentic-topbar">
        <QbeLogoWhite height={22} />
        <span style={{ marginLeft: 16, fontSize: 13, opacity: .85 }}>Cumulus Assistant</span>
        <button className="close" onClick={onExit} aria-label="Exit">
          <Icon name="x" size={14} style={{ marginRight: 6 }} /> Exit
        </button>
      </div>

      <div className="agentic-body">
        <ChatPanel
          messages={messages}
          isTyping={isTyping}
          onSendMessage={handleAnswer}
          onSubmitWidget={(payload) => handleAnswer(payload)}
          onClose={onExit}
          customerProfile={customerProfile}
        />
        <div className="view-pane">
          <div className="view-pane-inner">
            {stage !== 'CONFIRMATION' && <ProgressRail currentIndex={progressIndex} />}
            {renderView()}
          </div>
          {showLivePrice && (
            <LivePriceBanner
              tier={answers.quote.tier === 'ThirdPartyProperty' ? 'Third Party Property' : 'Comprehensive'}
              vehicleLabel={answers.vehicle?.lookup?.displayTitle || 'VW Golf'}
              suburb={priceSuburb}
              price={livePrice}
            />
          )}
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { AgenticExperience });
