import { StrictMode, useMemo, useRef, useState } from "react";
import { createRoot } from "react-dom/client";
import { acceptBookingQuote, BookingQuoteRequest, ProductClient, bookingQuote, sessionTransport } from "@product/client";
import { calculateQuote } from "@product/client/local";
import "./index.css";

const client = new ProductClient(sessionTransport());

export function BookingQuote() {
  const [unitPrice, setUnitPrice] = useState("1250");
  const [units, setUnits] = useState("2");
  const [remoteTotal, setRemoteTotal] = useState<string | null>(null);
  const [accepted, setAccepted] = useState(false);
  const [operationError, setOperationError] = useState<string | null>(null);
  const [requestId, setRequestId] = useState("900719925474099312345");
  const [discount, setDiscount] = useState("1250");
  const [tax, setTax] = useState("2000");
  const inputRevision = useRef(0);
  const verificationSequence = useRef(0);
  const loadSequence = useRef(0);
  const isAcceptanceInFlight = useRef(false);
  const [isAccepting, setIsAccepting] = useState(false);
  const calculation = useMemo(() => {
    try {
      return { quote: calculateQuote({
        request_id: requestId,
        unit_price_minor: BigInt(unitPrice),
        units: BigInt(units),
        discount_bps: BigInt(discount),
        tax_bps: BigInt(tax),
      }), error: null };
    } catch (cause) {
      return { quote: null, error: cause instanceof Error ? cause.message : String(cause) };
    }
  }, [requestId, unitPrice, units, discount, tax]);
  const local = calculation.quote;

  function edit(setter: (value: string) => void, value: string) {
    inputRevision.current += 1;
    setter(value);
    setRemoteTotal(null);
    setAccepted(false);
    setOperationError(null);
  }

  async function loadCurrentTerms() {
    const revision = inputRevision.current;
    const sequence = ++loadSequence.current;
    try {
      setOperationError(null);
      const { value } = await BookingQuoteRequest.bind(client.transport).get(requestId);
      if (inputRevision.current !== revision || loadSequence.current !== sequence) return;
      inputRevision.current += 1;
      setUnitPrice(value.unit_price_minor.toString());
      setUnits(value.units.toString());
      setDiscount(value.discount_bps.toString());
      setTax(value.tax_bps.toString());
      setRemoteTotal(null);
      setAccepted(false);
    } catch (cause) {
      if (inputRevision.current !== revision || loadSequence.current !== sequence) return;
      setOperationError(cause instanceof Error ? cause.message : String(cause));
    }
  }

  async function verifyAuthoritativeQuote() {
    const revision = inputRevision.current;
    const sequence = ++verificationSequence.current;
    const inputRequestId = requestId;
    try {
      setOperationError(null);
      const rows = await bookingQuote.run(client, { request_id: inputRequestId });
      if (inputRevision.current !== revision || verificationSequence.current !== sequence) return;
      setRemoteTotal(rows[0]?.total_minor.toString() ?? null);
    } catch (cause) {
      if (inputRevision.current !== revision || verificationSequence.current !== sequence) return;
      setRemoteTotal(null);
      setOperationError(cause instanceof Error ? cause.message : String(cause));
    }
  }

  async function acceptCurrentQuote() {
    if (local === null || isAcceptanceInFlight.current) return;
    isAcceptanceInFlight.current = true;
    setIsAccepting(true);
    const revision = inputRevision.current;
    const input = {
      request_id: requestId, unit_price_minor: BigInt(unitPrice), units: BigInt(units),
      discount_bps: BigInt(discount), tax_bps: BigInt(tax),
    };
    try {
      setOperationError(null);
      const plan = await acceptBookingQuote.plan(client, input, { targetKey: requestId });
      if (inputRevision.current !== revision) return;
      await acceptBookingQuote.execute(client, plan, { planHash: plan.hash });
      if (inputRevision.current !== revision) return;
      setAccepted(true);
    } catch (cause) {
      if (inputRevision.current !== revision) return;
      setAccepted(false);
      setOperationError(cause instanceof Error ? cause.message : String(cause));
    } finally {
      isAcceptanceInFlight.current = false;
      setIsAccepting(false);
    }
  }

  return <main>
    <h1>Booking Quote</h1>
    <label>Request ID<input aria-label="Request ID" value={requestId} onChange={(event) => edit(setRequestId, event.target.value)} /></label>
    <label>Unit price (minor units)<input aria-label="Unit price" value={unitPrice} onChange={(event) => edit(setUnitPrice, event.target.value)} /></label>
    <label>Units<input aria-label="Units" value={units} onChange={(event) => edit(setUnits, event.target.value)} /></label>
    <label>Discount (basis points)<input aria-label="Discount" value={discount} onChange={(event) => edit(setDiscount, event.target.value)} /></label>
    <label>Tax (basis points)<input aria-label="Tax" value={tax} onChange={(event) => edit(setTax, event.target.value)} /></label>
    <output aria-label="Local total">{local?.total_minor.toString() ?? ""}</output>
    <output aria-label="Quote error">{calculation.error ?? ""}</output>
    <button onClick={loadCurrentTerms}>Load current terms</button>
    <button onClick={verifyAuthoritativeQuote}>Verify authoritative quote</button>
    <button disabled={local === null || isAccepting} onClick={acceptCurrentQuote}>Accept current quote</button>
    <output aria-label="Operation error">{operationError ?? ""}</output>
    {remoteTotal === null ? null : <output aria-label="Remote total">{remoteTotal}</output>}
    {accepted ? <output aria-label="Accepted">accepted</output> : null}
  </main>;
}

const root = document.getElementById("root");
if (root !== null) {
  createRoot(root).render(<StrictMode><BookingQuote /></StrictMode>);
}
