// Multi-step quote form
//
// Two ways in, one destination. A visitor either shapes their building in the
// FabTrk 3D designer (which hands its spec over via app.jsx's postMessage
// bridge) or skips it entirely and sends us their plans. Both paths finish in
// this form and both submit to FabTrk as a real quote: the 3D path as a
// 'parametric' quote carrying its configuration, the upload path as an 'intake'
// quote carrying documents.

const QUOTE_API_URL =
  (typeof window !== 'undefined' && window.FABTRK_API_URL) || 'https://api.fabtrk.com';

/**
 * The org embed token, what makes a submission land in Chambliss's FabTrk
 * inbox. Set explicitly in index.html; falls back to the `?org=` already on the
 * designer URL so the two can't drift apart if only one is updated.
 */
function fabtrkEmbedToken() {
  if (typeof window === 'undefined') return '';
  if (window.FABTRK_EMBED_TOKEN) return window.FABTRK_EMBED_TOKEN;
  try {
    return new URL(window.FABTRK_DESIGNER_URL).searchParams.get('org') || '';
  } catch (e) {
    return '';
  }
}

// Upload limits, matched to what the FabTrk intake route accepts. Enforced here
// too so a visitor is told immediately rather than after a 30-second upload.
const MAX_FILES = 8;
const MAX_FILE_BYTES = 32 * 1024 * 1024;
const ACCEPTED_FILES = 'image/jpeg,image/png,image/gif,image/webp,image/heic,.pdf,.dxf,.dwg';

const readFileAsDataUrl = (file) =>
  new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = () => resolve(reader.result);
    reader.onerror = () => reject(new Error(`Could not read ${file.name}`));
    reader.readAsDataURL(file);
  });

const fmtBytes = (n) => {
  if (!n) return '';
  if (n < 1024 * 1024) return `${Math.round(n / 1024)} KB`;
  return `${(n / (1024 * 1024)).toFixed(1)} MB`;
};

const BUILDING_TYPES = [
  'Agricultural', 'Aircraft Hangar', 'Auto Shop', 'Barn',
  'Barndominium / Home', 'Brewery', 'Car Wash', 'Carport',
  'Casino', 'Church', 'Climate Controlled Storage', 'Commercial',
  'Fitness Center', 'Garage', 'Golf Cart Storage', 'Gymnasium',
  'Hay Storage', 'Horse Stable', 'Industrial', 'Institutional',
  'Manufacturing', 'Medical', 'Mini Storage', 'Office',
  'Outbuilding', 'Rec Center', 'Retail', 'Riding Arena',
  'RV Garage', 'RV Storage', 'School', 'Shed',
  'Sports Facility', 'Storage', 'Strip Mall', 'Warehouse',
  'Workshop', 'Other',
];

const STATES = ['Alabama','Alaska','Arizona','Arkansas','California','Colorado','Connecticut','Delaware','Florida','Georgia','Hawaii','Idaho','Illinois','Indiana','Iowa','Kansas','Kentucky','Louisiana','Maine','Maryland','Massachusetts','Michigan','Minnesota','Mississippi','Missouri','Montana','Nebraska','Nevada','New Hampshire','New Jersey','New Mexico','New York','North Carolina','North Dakota','Ohio','Oklahoma','Oregon','Pennsylvania','Rhode Island','South Carolina','South Dakota','Tennessee','Texas','Utah','Vermont','Virginia','Washington','West Virginia','Wisconsin','Wyoming'];

// The two paths have different step lists: the upload path is deliberately
// short, because someone who already has drawings shouldn't be walked through
// three screens of choices to hand them over.
const STEPS_DESIGN = [
  { n: '01', t: 'Use case' },
  { n: '02', t: 'Dimensions' },
  { n: '03', t: 'Spec' },
  { n: '04', t: 'Site' },
  { n: '05', t: 'Contact' },
];
const STEPS_UPLOAD = [
  { n: '01', t: 'Your project' },
  { n: '02', t: 'Site' },
  { n: '03', t: 'Contact' },
];

const QuoteSteps = ({ steps, current }) => {
  return (
    <>
      {/* Desktop stepper */}
      <div className="steps">
        {steps.map((s, i) => (
          <React.Fragment key={s.n}>
            <div className={`step ${current === i ? 'active' : current > i ? 'done' : ''}`}>
              <span className="n">{current > i ? '✓' : s.n}</span>
              <span>{s.t}</span>
            </div>
            {i < steps.length - 1 && <div className="bar"></div>}
          </React.Fragment>
        ))}
      </div>
      {/* Mobile compact stepper */}
      <div className="steps-mobile" style={{ display: 'none', alignItems: 'center', gap: 12 }}>
        <span className="mono" style={{ fontSize: 12, color: 'var(--fg)' }}>
          {steps[current].t}
        </span>
        <span className="mono" style={{ fontSize: 11, color: 'var(--fg-faint)' }}>
          {current + 1} / {steps.length}
        </span>
        <div style={{ flex: 1, height: 3, background: 'var(--border)', borderRadius: 2, overflow: 'hidden' }}>
          <div style={{ width: `${((current + 1) / steps.length) * 100}%`, height: '100%', background: 'var(--accent)', transition: 'width .3s' }}></div>
        </div>
      </div>
    </>
  );
};

const TypeCard = ({ label, selected, onClick }) => (
  <button
    type="button"
    onClick={onClick}
    style={{
      padding: '18px 16px',
      background: selected ? 'rgba(107,166,207,0.12)' : 'var(--bg-card)',
      border: `1px solid ${selected ? 'var(--accent)' : 'var(--border)'}`,
      color: selected ? 'var(--fg)' : 'var(--fg-dim)',
      textAlign: 'left',
      fontSize: 14,
      fontFamily: 'inherit',
      cursor: 'pointer',
      transition: 'all .15s',
      display: 'flex', alignItems: 'center', gap: 10,
    }}
  >
    <span style={{
      width: 14, height: 14, border: `1px solid ${selected ? 'var(--accent)' : 'var(--border-strong)'}`,
      display: 'inline-flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0,
    }}>
      {selected && <span style={{ width: 6, height: 6, background: 'var(--accent)' }}></span>}
    </span>
    {label}
  </button>
);

const Step1 = ({ form, update }) => (
  <div>
    <h2>What are you building?</h2>
    <p style={{ marginTop: 12, fontSize: 16, color: 'var(--fg-dim)' }}>Pick the closest fit. You can always tell us more in step five.</p>
    <div className="mob-2col" style={{
      marginTop: 36, display: 'grid',
      gridTemplateColumns: 'repeat(4, 1fr)', gap: 10,
    }}>
      {BUILDING_TYPES.map(t => (
        <TypeCard key={t} label={t} selected={form.buildingType === t} onClick={() => update({ buildingType: t })} />
      ))}
    </div>
    {form.buildingType === 'Other' && (
      <div className="field" style={{ marginTop: 24, maxWidth: 480 }}>
        <label htmlFor="q-other-type">Tell us more</label>
        <input id="q-other-type" name="otherType" className="input" placeholder="e.g. distillery, observatory, indoor pickleball" value={form.otherType || ''} onChange={e => update({ otherType: e.target.value })} />
      </div>
    )}
  </div>
);

const DimSlider = ({ label, unit, min, max, step, value, onChange, suffix }) => {
  // htmlFor/id ties the visible label to the range input; aria-valuetext makes
  // it announce "40 feet" rather than a bare "40".
  const id = React.useId();
  return (
    <div style={{ padding: 24, border: '1px solid var(--border)', background: 'var(--bg-card)' }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}>
        <label htmlFor={id} className="mono" style={{ fontSize: 11, color: 'var(--fg-faint)', textTransform: 'uppercase', letterSpacing: '0.08em' }}>
          {label}
        </label>
        <span className="mono" style={{ fontSize: 22, color: 'var(--fg)' }}>
          {value}<span style={{ fontSize: 14, color: 'var(--fg-faint)', marginLeft: 4 }}>{unit}</span>
        </span>
      </div>
      <input
        id={id}
        type="range"
        min={min} max={max} step={step}
        value={value}
        aria-valuetext={`${value} ${unit}`}
        onChange={e => onChange(+e.target.value)}
        style={{
          width: '100%', marginTop: 18, accentColor: 'var(--accent)',
        }}
      />
      <div className="mono" style={{ display: 'flex', justifyContent: 'space-between', fontSize: 10, color: 'var(--fg-faint)', marginTop: 6 }} aria-hidden="true">
        <span>{min}{unit}</span>
        <span>{suffix}</span>
        <span>{max}{unit}</span>
      </div>
    </div>
  );
};

const Step2 = ({ form, update }) => {
  const area = (form.width || 0) * (form.length || 0);
  const minWarning = area < 3500;
  return (
    <div>
      <h2>How big does it need to be?</h2>
      <p style={{ marginTop: 12, fontSize: 16, color: 'var(--fg-dim)' }}>3,500 sq ft minimum. Don't worry about exact, we'll dial it in.</p>

      <div className="mob-stack" style={{ marginTop: 36, display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 16 }}>
        <DimSlider label="Width" unit="ft" min={20} max={300} step={5} value={form.width} onChange={(v) => update({ width: v })} suffix="W" />
        <DimSlider label="Length" unit="ft" min={20} max={500} step={5} value={form.length} onChange={(v) => update({ length: v })} suffix="L" />
        <DimSlider label="Wall height" unit="ft" min={8} max={32} step={1} value={form.height} onChange={(v) => update({ height: v })} suffix="H" />
      </div>

      {/* Visual */}
      <div style={{ marginTop: 32, padding: 32, border: '1px solid var(--border)', background: 'var(--bg-card)', position: 'relative', overflow: 'hidden' }}>
        <div className="mono" style={{ fontSize: 10, color: 'var(--fg-faint)', letterSpacing: '0.08em' }}>
          FIG. 02 / ELEVATION (NOT TO SCALE)
        </div>
        <div style={{ display: 'flex', alignItems: 'flex-end', justifyContent: 'center', marginTop: 24, height: 200, position: 'relative' }}>
          <svg viewBox="0 0 600 220" style={{ width: '100%', maxWidth: 560, height: 'auto', overflow: 'visible' }}>
            {(() => {
              const aspect = form.length / form.width;
              const w = Math.min(440, 200 + aspect * 80);
              const h = 70 + (form.height - 8) * 5;
              // Roof peak rise driven by the selected pitch (rise:12 run), scaled
              // to the drawn span. "Unknown" pitch keeps a neutral roof.
              const pitchN = parseFloat(form.pitch);
              const roof = Number.isFinite(pitchN) ? Math.max(6, Math.min(80, (pitchN / 12) * (w / 2))) : 30;
              const cx = 300;
              const x = cx - w/2;
              const y = 200 - h - roof;
              return (
                <g>
                  {/* Ground */}
                  <line x1="40" y1="200" x2="560" y2="200" stroke="var(--fg-faint)" strokeWidth="1" />
                  {[60,80,100,120,140,160,180,200,220,240,260,280,300,320,340,360,380,400,420,440,460,480,500,520,540].map(gx => (
                    <line key={gx} x1={gx} y1="200" x2={gx-6} y2="208" stroke="var(--fg-faint)" strokeWidth="0.5" />
                  ))}
                  {/* Building outline */}
                  <rect x={x} y={y+roof} width={w} height={h} fill="rgba(107,166,207,0.08)" stroke="var(--accent)" strokeWidth="1.5" />
                  {/* Roof */}
                  <polygon points={`${x},${y+roof} ${cx},${y} ${x+w},${y+roof}`} fill="rgba(107,166,207,0.14)" stroke="var(--accent)" strokeWidth="1.5" />
                  {/* Door */}
                  <rect x={cx - 22} y={200 - 44} width={44} height={44} fill="var(--bg)" stroke="var(--fg-dim)" strokeWidth="1" />
                  {/* Width dim */}
                  <line x1={x} y1={210} x2={x+w} y2={210} stroke="var(--fg-dim)" strokeWidth="0.5" />
                  <line x1={x} y1={206} x2={x} y2={214} stroke="var(--fg-dim)" strokeWidth="0.5" />
                  <line x1={x+w} y1={206} x2={x+w} y2={214} stroke="var(--fg-dim)" strokeWidth="0.5" />
                  <text x={cx} y={224} textAnchor="middle" fontFamily="Geist Mono, monospace" fontSize="10" fill="var(--fg-dim)">{form.width}′ × {form.length}′</text>
                  {/* Height dim */}
                  <line x1={x - 18} y1={200} x2={x - 18} y2={y+roof} stroke="var(--fg-dim)" strokeWidth="0.5" />
                  <line x1={x - 22} y1={200} x2={x - 14} y2={200} stroke="var(--fg-dim)" strokeWidth="0.5" />
                  <line x1={x - 22} y1={y+roof} x2={x - 14} y2={y+roof} stroke="var(--fg-dim)" strokeWidth="0.5" />
                  <text x={x - 24} y={(y+roof+200)/2 + 3} textAnchor="end" fontFamily="Geist Mono, monospace" fontSize="10" fill="var(--fg-dim)">{form.height}′</text>
                </g>
              );
            })()}
          </svg>
        </div>
        <div style={{ marginTop: 16, display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: 12 }}>
          <span className="mono" style={{ fontSize: 12, color: 'var(--fg-dim)' }}>
            FOOTPRINT / {(form.width * form.length).toLocaleString()} SQ FT
          </span>
          {minWarning && (
            <span className="mono" style={{ fontSize: 12, color: 'var(--warn)' }}>
              ⚠ BELOW 3,500 SQ FT MIN / WE'LL DISCUSS
            </span>
          )}
        </div>
      </div>
    </div>
  );
};

const ChipGroup = ({ label, options, value, onChange }) => {
  // These chips are a single-select set, so they are exposed as a radiogroup.
  // Before this the <label> pointed at nothing (buttons are not labelable) and
  // the selected chip was conveyed by colour alone, which a screen reader and
  // anyone with a colour-vision deficiency both miss.
  const labelId = React.useId();
  return (
  <div className="field" role="radiogroup" aria-labelledby={labelId}>
    <span id={labelId} className="field-label">{label}</span>
    <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', marginTop: 4 }}>
      {options.map(o => {
        const selected = value === o;
        return (
          <button
            key={o}
            type="button"
            role="radio"
            aria-checked={selected}
            onClick={() => onChange(o)}
            style={{
              padding: '10px 14px',
              border: `1px solid ${selected ? 'var(--accent)' : 'var(--border)'}`,
              background: selected ? 'rgba(107,166,207,0.12)' : 'var(--bg-card)',
              color: selected ? 'var(--fg)' : 'var(--fg-dim)',
              fontSize: 13,
              fontFamily: 'inherit',
              cursor: 'pointer',
              transition: 'all .15s',
            }}
          >
            {o}
          </button>
        );
      })}
    </div>
  </div>
  );
};

const Step3 = ({ form, update }) => (
  <div>
    <h2>Spec it out.</h2>
    <p style={{ marginTop: 12, fontSize: 16, color: 'var(--fg-dim)' }}>Pick the panel system, color, and insulation. Not sure? Skip, we'll guide you on the call.</p>

    <div style={{ marginTop: 36, display: 'flex', flexDirection: 'column', gap: 32 }}>
      <ChipGroup
        label="Insulation"
        options={['Minimum R-Value', 'Best R-Value', 'None']}
        value={form.insulation}
        onChange={v => update({ insulation: v })}
      />
      <ChipGroup
        label="Roof color"
        options={['Colored', 'Galvalume']}
        value={form.roofColor}
        onChange={v => update({ roofColor: v })}
      />
      <ChipGroup
        label="Roof panel type"
        options={['PBR', 'Standing Seam']}
        value={form.roofPanel}
        onChange={v => update({ roofPanel: v })}
      />
      {/* GenStone is the manufactured-stone panel we stock and install, so it is
          offered by name rather than as a generic "stone accent". */}
      <ChipGroup
        label="Wainscot"
        options={['GenStone stone', 'Steel wainscot', 'None']}
        value={form.wainscot}
        onChange={v => update({ wainscot: v })}
      />
    </div>

    {form.wainscot === 'GenStone stone' && (
      <div style={{ marginTop: 24, padding: 20, border: '1px solid var(--border)', background: 'var(--bg-card)' }}>
        <div className="mono" style={{ fontSize: 11, color: 'var(--accent)', letterSpacing: '0.08em' }}>
          GENSTONE® WAINSCOT
        </div>
        <p style={{ marginTop: 10, fontSize: 13.5, lineHeight: 1.55, color: 'var(--fg-dim)' }}>
          Manufactured stone panels that fasten straight to the wall, no mortar and no mason.
          We supply and install it. Tell us in step four how far up you want it and whether to
          wrap columns or a gable end.
        </p>
      </div>
    )}

    {/* Color swatch row when "Colored" selected */}
    {form.roofColor === 'Colored' && (
      <div style={{ marginTop: 32, padding: 24, border: '1px solid var(--border)', background: 'var(--bg-card)' }}>
        <div className="mono" style={{ fontSize: 11, color: 'var(--fg-faint)', letterSpacing: '0.08em', marginBottom: 16 }}>
          STANDARD COLORS / 26 GA · FINAL SELECTION FROM ACTUAL CHIPS
        </div>
        <div className="mob-4col" style={{ display: 'grid', gridTemplateColumns: 'repeat(8, 1fr)', gap: 8 }}>
          {/* Shared with the services page's finish chart, see components.jsx. */}
          {STEEL_COLORS.map(([c, n]) => (
            <div key={n} style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
              <div style={{ height: 48, background: c, border: '1px solid var(--border)' }}></div>
              <span className="mono" style={{ fontSize: 9, color: 'var(--fg-dim)', textTransform: 'uppercase', letterSpacing: '0.06em' }}>{n}</span>
            </div>
          ))}
        </div>
      </div>
    )}
  </div>
);

const Step4 = ({ form, update }) => (
  <div>
    <h2>Where, when, who.</h2>
    <p style={{ marginTop: 12, fontSize: 16, color: 'var(--fg-dim)' }}>Location for permitting, your timeline, and who's putting it up.</p>

    <div className="mob-stack" style={{ marginTop: 36, display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 20 }}>
      <div className="field">
        <label htmlFor="q-state">State <span className="req" aria-hidden="true">*</span></label>
        <select id="q-state" name="state" required aria-required="true" autoComplete="address-level1" className="select" value={form.state} onChange={e => update({ state: e.target.value })}>
          <option value="">Select state</option>
          {STATES.map(s => <option key={s}>{s}</option>)}
        </select>
      </div>
      <div className="field">
        <label htmlFor="q-city">City</label>
        <input id="q-city" name="city" autoComplete="address-level2" className="input" placeholder="e.g. Live Oak" value={form.city || ''} onChange={e => update({ city: e.target.value })} />
      </div>
      <div className="field">
        <label htmlFor="q-zip">Zip code</label>
        <input id="q-zip" name="zip" inputMode="numeric" autoComplete="postal-code" className="input" placeholder="32060" value={form.zip || ''} onChange={e => update({ zip: e.target.value })} />
      </div>
      <div className="field">
        <label htmlFor="q-county">County / region</label>
        <input id="q-county" name="county" className="input" placeholder="Suwannee" value={form.county || ''} onChange={e => update({ county: e.target.value })} />
      </div>
    </div>

    <div className="mob-stack" style={{ marginTop: 32, display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 32 }}>
      <ChipGroup
        label="Desired timeline"
        options={['ASAP', '1-2 Months', '3-5 Months', '6-9 Months', 'Other']}
        value={form.timeline}
        onChange={v => update({ timeline: v })}
      />
      <ChipGroup
        label="Contractor"
        options={['I plan to construct myself (DIY)', 'I am a building contractor / erector', 'I need a contractor']}
        value={form.contractor}
        onChange={v => update({ contractor: v })}
      />
    </div>

    <div className="field" style={{ marginTop: 32 }}>
      <label htmlFor="q-notes">Additional details</label>
      <textarea id="q-notes" name="notes" className="textarea" rows="5" placeholder="Anything we should know, site access, special features, doors, sketches in your head…" value={form.notes || ''} onChange={e => update({ notes: e.target.value })}></textarea>
    </div>
  </div>
);

/**
 * Multi-file drop zone. Files are read into `data:` URIs up front so the whole
 * submission travels as one JSON POST, the same inline-bytes convention FabTrk
 * uses for document uploads, which keeps this page free of multipart handling.
 *
 * Oversized or over-count files are refused HERE, with a visible reason, rather
 * than silently by the server after a long upload. The bytes are held in form
 * state, so leaving and returning to this step doesn't lose them.
 */
const MultiFileDrop = ({ form, update, label, hint }) => {
  const fileInput = React.useRef(null);
  const [reading, setReading] = React.useState(false);
  const [fileError, setFileError] = React.useState('');
  const labelId = React.useId();
  const files = form.files || [];

  const addFiles = async (incoming) => {
    const list = Array.from(incoming || []);
    if (!list.length) return;
    setFileError('');
    const errors = [];
    const room = MAX_FILES - files.length;
    if (room <= 0) {
      setFileError(`That's the limit of ${MAX_FILES} files. Remove one to add another.`);
      return;
    }
    const accepted = [];
    for (const file of list.slice(0, room)) {
      if (file.size > MAX_FILE_BYTES) {
        errors.push(`${file.name} is ${fmtBytes(file.size)}. The limit is 32 MB.`);
        continue;
      }
      if (files.some((f) => f.name === file.name && f.size === file.size)) continue; // already added
      accepted.push(file);
    }
    if (list.length > room) errors.push(`Only the first ${room} file${room > 1 ? 's' : ''} were added (limit ${MAX_FILES}).`);

    if (accepted.length) {
      setReading(true);
      try {
        const read = await Promise.all(
          accepted.map(async (file) => ({
            name: file.name,
            size: file.size,
            dataUri: await readFileAsDataUrl(file),
          }))
        );
        update({ files: [...files, ...read] });
      } catch (err) {
        errors.push(err.message || 'One of those files could not be read.');
      } finally {
        setReading(false);
      }
    }
    if (errors.length) setFileError(errors.join(' '));
  };

  return (
    <div className="field">
      <span className="field-label" id={labelId}>
        {label} {hint && <span style={{ color: 'var(--fg-faint)' }}>{hint}</span>}
      </span>
      {/* A real <button>, not a <div onClick>: it needs a tab stop, Enter/Space,
          and an accessible name. The file input is visually hidden rather than
          display:none so assistive tech targeting it directly still finds it. */}
      <button
        type="button"
        aria-describedby={labelId}
        onClick={() => fileInput.current?.click()}
        onDragOver={(e) => { e.preventDefault(); }}
        onDrop={(e) => { e.preventDefault(); addFiles(e.dataTransfer.files); }}
        style={{
          display: 'block', width: '100%', font: 'inherit', color: 'inherit',
          border: '1px dashed var(--border-strong)', background: 'var(--bg-card)',
          padding: files.length ? 24 : 40, textAlign: 'center', cursor: 'pointer',
          transition: 'border-color .15s',
        }}
      >
        <input
          ref={fileInput}
          type="file"
          multiple
          aria-label="Upload drawings, sketches, or photos"
          accept={ACCEPTED_FILES}
          className="visually-hidden"
          onChange={(e) => { addFiles(e.target.files); e.target.value = ''; }}
        />
        <span style={{ display: 'block', fontSize: 14, color: 'var(--fg-dim)' }}>
          {reading ? 'Reading files…' : files.length ? 'Add another file' : 'Drop files here or click to select'}
        </span>
        <span className="mono" style={{ display: 'block', fontSize: 11, color: 'var(--fg-faint)', marginTop: 8, letterSpacing: '0.08em' }}>
          PDF · DWG · DXF · JPG · PNG · UP TO 32 MB EACH · {MAX_FILES} MAX
        </span>
      </button>

      {/* Siblings, not nested: a button inside a button is invalid markup and the
          inner one is unreachable by keyboard. */}
      {files.length > 0 && (
        <ul style={{ listStyle: 'none', margin: '10px 0 0', padding: 0, display: 'flex', flexDirection: 'column', gap: 6 }}>
          {files.map((f, i) => (
            <li key={`${f.name}-${i}`} style={{
              display: 'flex', alignItems: 'center', gap: 10,
              border: '1px solid var(--border)', background: 'var(--bg-card)', padding: '10px 12px',
            }}>
              <svg width="18" height="18" viewBox="0 0 20 20" fill="none" aria-hidden="true" style={{ flexShrink: 0 }}><path d="M5 17 V3 H12 L17 8 V17 Z" stroke="var(--accent)" strokeWidth="1.4" /><path d="M12 3 V8 H17" stroke="var(--accent)" strokeWidth="1.4" /></svg>
              <span style={{ fontSize: 13, flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{f.name}</span>
              <span className="mono" style={{ fontSize: 11, color: 'var(--fg-faint)', flexShrink: 0 }}>{fmtBytes(f.size)}</span>
              <button
                type="button"
                onClick={() => update({ files: files.filter((_, j) => j !== i) })}
                aria-label={`Remove ${f.name}`}
                style={{ background: 'none', border: 'none', color: 'var(--fg-dim)', fontSize: 12, cursor: 'pointer', textDecoration: 'underline', flexShrink: 0 }}
              >
                Remove
              </button>
            </li>
          ))}
        </ul>
      )}
      {fileError && (
        <p className="mono" style={{ marginTop: 8, fontSize: 12, color: 'var(--warn)' }} role="alert">{fileError}</p>
      )}
    </div>
  );
};

/**
 * The upload path's one substantive screen: what it is, roughly how big (only if
 * they happen to know), and their drawings. No sliders, no panel systems, no
 * color swatches: the whole point of this door is that the customer doesn't
 * want to specify a building, they want to hand over the one they already have.
 */
const StepProject = ({ form, update }) => (
  <div>
    <h2>Tell us about the project.</h2>
    <p style={{ marginTop: 12, fontSize: 16, color: 'var(--fg-dim)' }}>
      Send us whatever you have: stamped plans, a sketch, or a photo. We'll take
      it from there.
    </p>

    <div style={{ marginTop: 36 }}>
      <MultiFileDrop form={form} update={update} label="Plans, drawings, or photos" hint="(this is the fast way)" />
    </div>

    <div className="field" style={{ marginTop: 32 }}>
      <label htmlFor="q-type">What are you building?</label>
      <select id="q-type" name="buildingType" className="select" value={form.buildingType || ''} onChange={(e) => update({ buildingType: e.target.value })}>
        <option value="">Select the closest fit</option>
        {BUILDING_TYPES.map((t) => <option key={t}>{t}</option>)}
      </select>
    </div>

    {form.buildingType === 'Other' && (
      <div className="field" style={{ marginTop: 20, maxWidth: 480 }}>
        <label htmlFor="q-other-type-2">Tell us more</label>
        <input id="q-other-type-2" name="otherType" className="input" placeholder="e.g. distillery, observatory, indoor pickleball" value={form.otherType || ''} onChange={(e) => update({ otherType: e.target.value })} />
      </div>
    )}

    {/* Optional, and left blank by default on purpose: sending the shop a size
        the customer never actually typed would be worse than sending none. */}
    <fieldset style={{ marginTop: 32, border: '1px solid var(--border)', background: 'var(--bg-card)', padding: 20 }}>
      <legend className="mono" style={{ fontSize: 11, color: 'var(--fg-faint)', textTransform: 'uppercase', letterSpacing: '0.08em', padding: '0 8px' }}>
        Size
      </legend>
      <div className="mob-stack" style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 16 }}>
        {[
          ['sizeWidth', 'Width (ft)', '60'],
          ['sizeLength', 'Length (ft)', '100'],
          ['sizeHeight', 'Wall height (ft)', '14'],
        ].map(([key, label, placeholder]) => (
          <div className="field" key={key}>
            <label htmlFor={`q-${key}`}>{label}</label>
            <input
              id={`q-${key}`}
              name={key}
              className="input"
              inputMode="numeric"
              placeholder={placeholder}
              value={form[key] || ''}
              onChange={(e) => update({ [key]: e.target.value.replace(/[^\d]/g, '') })}
            />
          </div>
        ))}
      </div>
      <p style={{ marginTop: 12, fontSize: 13, color: 'var(--fg-faint)' }}>
        If you don't know, we'll figure it out on the call.
      </p>
    </fieldset>
  </div>
);

const Step5 = ({ form, update }) => {
  return (
    <div>
      <h2>Last thing, how to reach you.</h2>
      <p style={{ marginTop: 12, fontSize: 16, color: 'var(--fg-dim)' }}>We will be in touch soon.</p>

      <div className="mob-stack" style={{ marginTop: 36, display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 20 }}>
        <div className="field">
          <label htmlFor="q-first">First name <span className="req" aria-hidden="true">*</span></label>
          <input id="q-first" name="firstName" required aria-required="true" autoComplete="given-name" className="input" value={form.firstName || ''} onChange={e => update({ firstName: e.target.value })} />
        </div>
        <div className="field">
          <label htmlFor="q-last">Last name <span className="req" aria-hidden="true">*</span></label>
          <input id="q-last" name="lastName" required aria-required="true" autoComplete="family-name" className="input" value={form.lastName || ''} onChange={e => update({ lastName: e.target.value })} />
        </div>
        <div className="field">
          <label htmlFor="q-email">Email <span className="req" aria-hidden="true">*</span></label>
          <input id="q-email" name="email" required aria-required="true" autoComplete="email" className="input" type="email" value={form.email || ''} onChange={e => update({ email: e.target.value })} />
        </div>
        <div className="field">
          <label htmlFor="q-phone">Phone <span className="req" aria-hidden="true">*</span></label>
          <input id="q-phone" name="phone" required aria-required="true" autoComplete="tel" className="input" type="tel" placeholder="(386) 555-0100" value={form.phone || ''} onChange={e => update({ phone: e.target.value })} />
        </div>
      </div>

      {/* Still offered on the 3D path: a designed building can still come with a
          site plan or a survey. The upload path collects these in step one. */}
      <div style={{ marginTop: 32 }}>
        <MultiFileDrop form={form} update={update} label="Drawings or photos" hint="(optional but helpful)" />
      </div>

      <div style={{ marginTop: 32, padding: 20, border: '1px solid var(--border)', background: 'var(--bg-card)', display: 'flex', gap: 12 }}>
        <input type="checkbox" id="agree" checked={form.agree || false} onChange={e => update({ agree: e.target.checked })} style={{ accentColor: 'var(--accent)', marginTop: 2 }} />
        <label htmlFor="agree" style={{ fontSize: 13, color: 'var(--fg-dim)', fontFamily: 'inherit', textTransform: 'none', letterSpacing: 0 }}>
          OK to call or text me about my project. We don't share contact info.
        </label>
      </div>
    </div>
  );
};

const QuoteSummary = ({ form, isUpload }) => {
  const location = `${form.city || '·'}, ${form.state || '·'}${form.zip ? ' ' + form.zip : ''}`;
  const files = form.files || [];
  // The upload path never asks about panels, insulation or wainscot, so echoing
  // the form's defaults back would show the visitor decisions they didn't make.
  const rows = isUpload
    ? [
        ['Building', form.buildingType ? form.buildingType + (form.otherType ? `, ${form.otherType}` : '') : ''],
        ['Documents', files.length ? `${files.length} file${files.length > 1 ? 's' : ''} attached` : ''],
        ['Size', form.sizeWidth && form.sizeLength
          ? `${form.sizeWidth}′ × ${form.sizeLength}′${form.sizeHeight ? ` × ${form.sizeHeight}′` : ''}`
          : "We'll read it off your plans"],
        ['Location', location],
        ['Timeline', form.timeline],
        ['Contractor', form.contractor],
      ]
    : [
        ['Use case', form.buildingType + (form.otherType ? `, ${form.otherType}` : '')],
        ['Dimensions', `${form.width}′ × ${form.length}′ × ${form.height}′`],
        ['Footprint', `${(form.width * form.length).toLocaleString()} sq ft`],
        ['Roof', `${form.roofPanel} · ${form.roofColor}`],
        ['Wainscot', form.wainscot],
        ['Insulation', form.insulation],
        ['Location', location],
        ['Timeline', form.timeline],
        ['Contractor', form.contractor],
        ...(files.length ? [['Documents', `${files.length} file${files.length > 1 ? 's' : ''} attached`]] : []),
      ];
  return (
    <aside className="quote-summary" style={{
      position: 'sticky', top: 96,
      border: '1px solid var(--border)',
      background: 'var(--bg-card)',
      padding: 24,
    }}>
      <div className="mono" style={{ fontSize: 11, color: 'var(--accent)', letterSpacing: '0.08em' }}>QUOTE / DRAFT</div>
      <h3 style={{ marginTop: 12, marginBottom: 4 }}>{isUpload ? 'Your request' : 'Your build'}</h3>
      <p style={{ fontSize: 13, color: 'var(--fg-faint)' }}>Updates as you go.</p>

      <div style={{ marginTop: 20, display: 'grid', gap: 0 }}>
        {rows.map(([k, v]) => (
          <div key={k} style={{
            display: 'grid', gridTemplateColumns: '1fr 1.4fr', gap: 12,
            padding: '10px 0', borderTop: '1px solid var(--border)',
          }}>
            <span className="mono" style={{ fontSize: 10, color: 'var(--fg-faint)', textTransform: 'uppercase', letterSpacing: '0.06em' }}>{k}</span>
            <span style={{ fontSize: 13, color: 'var(--fg)', wordBreak: 'break-word' }}>{v || '·'}</span>
          </div>
        ))}
      </div>

    </aside>
  );
};

/**
 * `receipt` is what FabTrk returned: the real quote number the shop will see, and
 * how many uploads actually stored. This screen used to print a random number
 * that existed nowhere: the whole point of a reference is that both sides can
 * look it up.
 */
const QuoteSent = ({ form, receipt }) => (
  <div style={{ textAlign: 'center', padding: '80px 32px', maxWidth: 720, margin: '0 auto' }}>
    <div style={{
      width: 64, height: 64, margin: '0 auto',
      border: '1px solid var(--accent)', display: 'flex', alignItems: 'center', justifyContent: 'center',
    }}>
      <svg width="28" height="28" viewBox="0 0 28 28" fill="none"><path d="M6 14 L12 20 L22 8" stroke="var(--accent)" strokeWidth="2"/></svg>
    </div>
    <h2 style={{ marginTop: 32 }}>Got it, {form.firstName || 'friend'}.</h2>
    <p style={{ marginTop: 16, fontSize: 18, color: 'var(--fg-dim)' }}>
      Your quote request is in. Someone from our team will be in touch soon.
    </p>
    <div style={{ marginTop: 40, padding: 24, border: '1px solid var(--border)', background: 'var(--bg-card)', textAlign: 'left' }}>
      <div className="mono" style={{ fontSize: 11, color: 'var(--accent)', letterSpacing: '0.08em' }}>REFERENCE</div>
      <div className="mono" style={{ marginTop: 8, fontSize: 22 }}>
        {receipt?.id || 'PENDING'}
      </div>
      <p style={{ marginTop: 12, fontSize: 13, color: 'var(--fg-faint)' }}>
        Quote this if we cross wires. It's the same number on our end.
        {receipt?.attachmentsStored
          ? ` We received ${receipt.attachmentsStored} file${receipt.attachmentsStored > 1 ? 's' : ''}.`
          : ''}
      </p>
      {/* An honest note beats a silent drop: the server refuses file types it
          can't store, and the visitor should hear it from us, now. */}
      {receipt && (form.files || []).length > (receipt.attachmentsStored ?? 0) && (
        <p className="mono" style={{ marginTop: 10, fontSize: 12, color: 'var(--warn)' }}>
          ⚠ {(form.files || []).length - (receipt.attachmentsStored ?? 0)} FILE(S) COULDN'T BE ACCEPTED ,
          CALL US AND WE'LL TAKE THEM BY EMAIL
        </p>
      )}
    </div>
    {/* flexWrap because this row is now three items wide and the phone breakpoint
        can't fit them on one line. */}
    <div style={{ marginTop: 32, display: 'flex', gap: 12, justifyContent: 'center', flexWrap: 'wrap' }}>
      <a className="btn" href="tel:+13866881121">
        <span className="mono">Call 386.688.1121</span>
      </a>
      {/* Best moment on the site to hand someone the brochure: they've just told
          us what they want and now have a day to wait for the callback. */}
      <a className="btn" href="assets/pdf/chambliss-building-brochure.pdf" target="_blank" rel="noopener noreferrer">
        Read the brochure
        <svg width="12" height="12" viewBox="0 0 12 12" fill="none" aria-hidden="true"><path d="M6 2 L6 8 M3 5.5 L6 8.5 L9 5.5 M2 10.5 L10 10.5" stroke="currentColor" strokeWidth="1.5"/></svg>
      </a>
      <button className="btn" onClick={() => window.dispatchEvent(new CustomEvent('nav', { detail: 'home' }))}>Back to home</button>
    </div>
  </div>
);

// Map the FabTrk configurator's "use" bucket onto the quote's building types.
const CONFIG_USE_TO_TYPE = {
  Commercial: 'Commercial',
  Agriculture: 'Agricultural',
  Equestrian: 'Riding Arena',
  Aviation: 'Aircraft Hangar',
  Barndominium: 'Barndominium / Home',
  Carport: 'Carport',
  Storage: 'Storage',
  'Garage / Shop': 'Garage',
  Warehouse: 'Warehouse',
  Church: 'Church',
};

const QUOTE_DEFAULTS = {
  buildingType: 'Agricultural',
  width: 60,
  length: 100,
  height: 14,
  pitch: '3:12',
  insulation: 'Best R-Value',
  roofColor: 'Colored',
  roofPanel: 'PBR',
  wainscot: 'None',
  state: '',
  timeline: '3-5 Months',
  contractor: 'I need a contractor',
};

// Read (and consume) a configuration handed over by the 3D designer via
// app.jsx's postMessage bridge. Cleared after reading so a later manual visit to
// the quote page doesn't resurface a stale prefill.
function consumeConfiguratorSpec() {
  if (typeof window === 'undefined') return null;
  const spec = window.__configuratorSpec;
  if (spec) { try { delete window.__configuratorSpec; } catch (e) { window.__configuratorSpec = null; } }
  return spec || null;
}

// Translate a designer spec into quote-form fields, dropping the finish/estimate
// (which the form has no field for) into the notes so the shop still sees them.
function specToForm(spec) {
  const num = (s) => { const n = parseInt(String(s).replace(/[^\d.]/g, ''), 10); return Number.isFinite(n) ? n : undefined; };
  const patch = {};
  if (spec.buildingUse && CONFIG_USE_TO_TYPE[spec.buildingUse]) patch.buildingType = CONFIG_USE_TO_TYPE[spec.buildingUse];
  const w = num(spec.width), l = num(spec.length), h = num(spec.eave);
  if (w) patch.width = w;
  if (l) patch.length = l;
  if (h) patch.height = h;
  if (spec.pitch) patch.pitch = spec.pitch; // e.g. "3:12", matches the select options
  if (spec.roof) patch.roofColor = /galvalume/i.test(spec.roof) ? 'Galvalume' : 'Colored';
  if (spec.insulation) patch.insulation = /best/i.test(spec.insulation) ? 'Best R-Value' : /min/i.test(spec.insulation) ? 'Minimum R-Value' : 'None';
  // The designer's wainscot value is a finish/colour string, not one of the
  // form's three choices, so bucket it. The raw value still rides along in the
  // notes below, so nothing the visitor picked is lost.
  if (spec.wainscot) patch.wainscot = /stone|genstone/i.test(String(spec.wainscot)) ? 'GenStone stone' : 'Steel wainscot';
  const openings = [spec.rollupDoor && 'roll-up door', spec.walkDoor && 'walk door', spec.windows && 'windows'].filter(Boolean).join(' + ');
  const addons = [
    spec.wainscot && `${spec.wainscot} wainscot`,
    spec.leanTo && `${spec.leanToFt}′ lean-to`,
    spec.eaveExtension && `${spec.eaveExtensionFt}′ eave extension`,
    spec.openSide && 'open sidewall',
    spec.gutters && 'gutters',
    spec.coating && spec.coating,
  ].filter(Boolean).join(', ');
  const bits = [
    `Designed in 3D: ${spec.buildingUse} ${spec.width} × ${spec.length} × ${spec.eave}, ${spec.pitch} pitch, ${spec.bay} bays`,
    `Finish: ${spec.roof} roof / ${spec.wall} walls${spec.insulation ? ` · ${spec.insulation} insulation` : ''}`,
  ];
  if (openings) bits.push(`Openings: ${openings}`);
  if (addons) bits.push(`Add-ons: ${addons}`);
  if (spec.estimate) bits.push(`Configurator ballpark: $${Number(spec.estimate).toLocaleString()}`);
  if (spec.designLink) bits.push(`3D design: ${spec.designLink}`);
  patch.notes = bits.join('. ') + '.';
  return patch;
}

/** "3:12" → 3. The form stores pitch as a ratio string; FabTrk wants the rise. */
const pitchRise = (p) => {
  const m = /^(\d+(?:\.\d+)?)/.exec(String(p || ''));
  return m ? parseFloat(m[1]) : undefined;
};

const posInt = (v) => {
  const n = parseInt(String(v ?? '').replace(/[^\d]/g, ''), 10);
  return Number.isFinite(n) && n > 0 ? n : undefined;
};

/**
 * Map this form onto FabTrk's public quote contract.
 *
 * The field set is FabTrk's (server/src/quotes/quotes.model.ts) and this is the
 * only place that knows how our labels correspond to it, the same job specToForm
 * does in the other direction for the configurator handoff. Keep the mapping
 * here rather than reshaping the form to match the API, so the page can keep
 * asking questions in the words a customer actually uses.
 *
 * Dimensions are sent only when they mean something: on the 3D path they came
 * from a building the visitor actually shaped, and on the upload path only if
 * they typed them. Passing the form's defaults along would tell the shop a
 * customer specified 60 × 100 × 14 when they never saw those numbers.
 */
function buildSubmission(form, designSpec) {
  const name = [form.firstName, form.lastName].filter(Boolean).join(' ').trim();
  const buildingType = [form.buildingType, form.buildingType === 'Other' ? form.otherType : null]
    .filter(Boolean)
    .join(': ');

  const width = designSpec ? posInt(form.width) : posInt(form.sizeWidth);
  const length = designSpec ? posInt(form.length) : posInt(form.sizeLength);
  const height = designSpec ? posInt(form.height) : posInt(form.sizeHeight);

  const payload = {
    customerName: name || form.email || 'Website visitor',
    email: form.email || undefined,
    phone: form.phone || undefined,
    city: form.city || undefined,
    state: form.state || undefined,
    zip: form.zip || undefined,
    county: form.county || undefined,
    buildingType: buildingType || undefined,
    timeline: form.timeline || undefined,
    contractor: form.contractor || undefined,
    message: form.notes || undefined,
    widthFt: width,
    lengthFt: length,
    eaveFt: height,
    pitch: pitchRise(form.pitch),
    sqft: width && length ? width * length : undefined,
    attachments: (form.files || []).map((f) => ({ name: f.name, dataUri: f.dataUri })),
  };

  if (designSpec) {
    // Carry the configuration through in the shape the FabTrk inbox rebuilds its
    // 3D preview from, so a quote finished on this site still opens as the exact
    // building the visitor designed, not just a paragraph describing it.
    payload.buildingUse = designSpec.buildingUse || undefined;
    payload.estimate = Number.isFinite(Number(designSpec.estimate)) ? Number(designSpec.estimate) : undefined;
    payload.designUrl = designSpec.designLink || undefined;
    payload.config = {
      widthFt: width,
      lengthFt: length,
      eaveFt: height,
      pitch: pitchRise(form.pitch),
      bayFt: posInt(designSpec.bay),
      rollupDoor: !!designSpec.rollupDoor,
      walkDoor: !!designSpec.walkDoor,
      windows: !!designSpec.windows,
      openWalls: Array.isArray(designSpec.openWalls) ? designSpec.openWalls : [],
      wainscot: !!designSpec.wainscot,
      leanTo: !!designSpec.leanTo,
      leanToSide: designSpec.leanToSide || 'right',
      leanToFt: posInt(designSpec.leanToFt) || 0,
      gutters: !!designSpec.gutters,
      coated: !!designSpec.coating,
      summary: designSpec,
    };
  }
  return payload;
}

/**
 * POST the quote. The 3D path submits as a 'parametric' quote (it has a real
 * configuration); the upload path submits as 'intake'. Separate endpoints, so
 * neither has to claim to be the other.
 */
async function submitQuote(form, designSpec) {
  const token = fabtrkEmbedToken();
  if (!token) throw new Error('This form isn\'t connected yet. Please call us at 386.688.1121.');
  const path = designSpec ? 'quotes' : 'intake';
  const res = await fetch(
    `${QUOTE_API_URL}/api/v1/public/embed/${encodeURIComponent(token)}/${path}`,
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(buildSubmission(form, designSpec)),
    }
  );
  const json = await res.json().catch(() => ({}));
  if (!res.ok) {
    if (res.status === 429) {
      throw new Error('We\'ve had a lot of requests from your network. Give it a few minutes, or call 386.688.1121.');
    }
    throw new Error(json?.error?.message || 'We couldn\'t send that. Please try again, or call 386.688.1121.');
  }
  return json?.data || {};
}

const DesignBanner = ({ spec }) => (
  <div style={{
    marginBottom: 32, padding: '16px 20px',
    border: '1px solid var(--accent)', background: 'rgba(107,166,207,0.10)',
    display: 'flex', alignItems: 'center', gap: 16, flexWrap: 'wrap',
  }}>
    <svg width="22" height="22" viewBox="0 0 28 28" fill="none" style={{ flexShrink: 0 }}><path d="M6 14 L12 20 L22 8" stroke="var(--accent)" strokeWidth="2"/></svg>
    <div style={{ flex: 1, minWidth: 220 }}>
      <div style={{ fontSize: 14, color: 'var(--fg)', fontWeight: 500 }}>Carried in from your 3D design.</div>
      <div className="mono" style={{ fontSize: 11, color: 'var(--fg-faint)', marginTop: 4, letterSpacing: '0.04em' }}>
        {spec.buildingUse} · {spec.width} × {spec.length} × {spec.eave} · {spec.pitch} · {spec.roof} ROOF{spec.estimate ? ` · ~$${Number(spec.estimate).toLocaleString()}` : ''}
      </div>
    </div>
    <span className="mono" style={{ fontSize: 11, color: 'var(--accent)' }}>REVIEW & FINISH ↓</span>
  </div>
);

/**
 * One of the two doors. Deliberately the same weight as the other: the previous
 * version gave the 3D designer an 82vh embed and the other path a small "skip"
 * link underneath, which read as "the real way, and the way for people who
 * couldn't manage it". Plenty of customers arrive with stamped drawings already
 * in hand. For them, uploading IS the direct route, not a fallback.
 */
const QuoteDoor = ({ eyebrow, title, body, bullets, cta, onClick, primary }) => (
  <button
    type="button"
    onClick={onClick}
    style={{
      display: 'flex', flexDirection: 'column', alignItems: 'flex-start', gap: 0,
      textAlign: 'left', font: 'inherit', color: 'inherit', cursor: 'pointer',
      padding: 28,
      background: primary ? 'rgba(107,166,207,0.06)' : 'var(--bg-card)',
      border: `1px solid ${primary ? 'var(--accent)' : 'var(--border)'}`,
      height: '100%', transition: 'border-color .15s, background .15s',
    }}
  >
    <span className="mono" style={{ fontSize: 11, color: 'var(--accent)', letterSpacing: '0.08em', textTransform: 'uppercase' }}>
      {eyebrow}
    </span>
    <span style={{ display: 'block', fontSize: 24, marginTop: 14, lineHeight: 1.15 }}>{title}</span>
    <span style={{ display: 'block', fontSize: 15, color: 'var(--fg-dim)', marginTop: 12, lineHeight: 1.5 }}>{body}</span>
    <ul style={{ listStyle: 'none', margin: '18px 0 0', padding: 0, display: 'flex', flexDirection: 'column', gap: 8 }}>
      {bullets.map((b) => (
        <li key={b} className="mono" style={{ fontSize: 11, color: 'var(--fg-faint)', letterSpacing: '0.04em', display: 'flex', gap: 8 }}>
          <span style={{ color: 'var(--accent)' }}>›</span>{b}
        </li>
      ))}
    </ul>
    <span className="mono" style={{ marginTop: 24, fontSize: 12, color: 'var(--fg)', display: 'flex', alignItems: 'center', gap: 8 }}>
      {cta}
      <svg width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M3 6 L9 6 M6 3 L9 6 L6 9" stroke="currentColor" strokeWidth="1.6"/></svg>
    </span>
  </button>
);

/**
 * Start-of-quote chooser. Both doors lead to the same form and the same inbox.
 * The 3D designer is an optional way to FILL IN the front of it, not a separate
 * process. Reuses home.jsx's ModelViewerFrame (loaded before this file).
 */
const QuoteChooser = ({ onShowDesigner, onStartUpload, showDesigner }) => (
  <section style={{ borderBottom: 'none', paddingBottom: 40 }}>
    <div className="page" style={{ paddingTop: 40, paddingBottom: 24 }}>
      <SectionEyebrow label="Start here" />
      <h2 style={{ maxWidth: 620, margin: 0 }}>Two ways to get a number.</h2>
      <p style={{ marginTop: 16, fontSize: 17, lineHeight: 1.5, maxWidth: 620, color: 'var(--fg-dim)' }}>
        Design it yourself in 3D, or send us what you already have. Either way it
        lands on the same desk and we will get back to you soon.
      </p>

      {!showDesigner && (
        <div className="mob-stack" style={{ marginTop: 36, display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 20 }}>
          <QuoteDoor
            primary
            eyebrow="Option A"
            title="Design it in 3D."
            body="Shape the building on screen and watch it reshape as you go. We carry the exact spec straight into your quote."
            bullets={['SET SIZE, COLORS, DOORS', 'SEE A BALLPARK AS YOU BUILD', 'ABOUT 3 MINUTES']}
            cta="Open the designer"
            onClick={onShowDesigner}
          />
          <QuoteDoor
            eyebrow="Option B"
            title="Send us what you've got."
            body="Already have plans, a sketch, or a quote from someone else? Upload it and skip the configurator entirely."
            bullets={['PDF, DWG, DXF, OR A PHOTO', 'NO SPECS TO FILL IN', 'ABOUT 1 MINUTE']}
            cta="Upload documents"
            onClick={onStartUpload}
          />
        </div>
      )}
    </div>

    {/* Full-width designer embed, revealed once Option A is chosen (same
        treatment as the configurator page). */}
    {showDesigner && (
      <>
        <ModelViewerFrame
          src={FABTRK_DESIGNER_URL}
          tag={null}
          title="Chambliss parametric building designer"
          cta="Design your building in 3D & AR"
          sub="SET DIMENSIONS · WATCH IT RESHAPE → INTO YOUR QUOTE"
          height="clamp(560px, 82vh, 900px)"
        />
        {/* "Skip to the form" used to sit on the right here, dropping the
            visitor into the long five-step wizard from step 0. Removed (Jul
            2026): there are two doors now, the designer and the upload, and a
            third manual path was the odd one out. The wizard's steps are NOT
            dead code - the designer hands off into them at step 3. */}
        <div className="page" style={{ paddingTop: 20, display: 'flex', gap: 12, flexWrap: 'wrap' }}>
          <button className="btn" onClick={onStartUpload} style={{ flexShrink: 0 }}>
            ← I'd rather just upload my plans
          </button>
        </div>
      </>
    )}
  </section>
);

const Quote = ({ setRoute }) => {
  const [designSpec, setDesignSpec] = React.useState(consumeConfiguratorSpec);
  // Begin on the chooser; if a spec was carried in from the standalone
  // configurator page, drop straight into the form.
  const [mode, setMode] = React.useState(designSpec ? 'form' : 'choose');
  // Which door the visitor came through. 'design' walks the long five-step
  // wizard (entered ONLY via the 3D designer handoff, which lands at step 3);
  // 'upload' is the three-step path for someone who already has drawings. It
  // decides the step list, the submit endpoint, and whether dimensions are
  // meaningful enough to send. 'full' is no longer reachable - the manual
  // "Skip to the form" entry was removed Jul 2026.
  const [path, setPath] = React.useState(designSpec ? 'design' : null);
  const [showDesigner, setShowDesigner] = React.useState(false);
  const [step, setStep] = React.useState(designSpec ? 3 : 0); // jump past use/dims/spec if pre-designed
  const [submitted, setSubmitted] = React.useState(false);
  const [receipt, setReceipt] = React.useState(null);
  const [sending, setSending] = React.useState(false);
  const [sendError, setSendError] = React.useState('');
  const [form, setForm] = React.useState(() => ({
    ...QUOTE_DEFAULTS,
    ...(designSpec ? specToForm(designSpec) : {}),
  }));
  const update = (patch) => setForm(f => ({ ...f, ...patch }));

  const isUpload = path === 'upload';
  const steps = isUpload ? STEPS_UPLOAD : STEPS_DESIGN;
  const lastStep = steps.length - 1;

  // If the visitor uses the 3D designer embedded on this page, its spec arrives
  // via app.jsx's bridge event: prefill the form, drop into it at the site step,
  // and clear the stashed spec so a later visit won't resurface it.
  React.useEffect(() => {
    const onSpec = (e) => {
      const spec = e.detail;
      if (!spec) return;
      setDesignSpec(spec);
      setForm((f) => ({ ...f, ...specToForm(spec) }));
      setMode('form');
      setPath('design');
      setStep(3);
      try { delete window.__configuratorSpec; } catch (_) { window.__configuratorSpec = null; }
    };
    window.addEventListener('configurator-spec', onSpec);
    return () => window.removeEventListener('configurator-spec', onSpec);
  }, []);

  const canNext = () => {
    if (isUpload) {
      // Nothing is required on the upload path's first screen: a visitor with a
      // stamped PDF and nothing else should still be able to get to Contact.
      if (step === lastStep) return form.firstName && form.lastName && form.email && form.phone && form.agree;
      return true;
    }
    if (step === 0) return !!form.buildingType;
    if (step === 1) return form.width && form.length && form.height;
    if (step === lastStep) return form.firstName && form.lastName && form.email && form.phone && form.agree;
    return true;
  };

  const send = async () => {
    if (!canNext() || sending) return;
    setSending(true);
    setSendError('');
    try {
      const data = await submitQuote(form, designSpec);
      setReceipt(data);
      setSubmitted(true);
    } catch (err) {
      setSendError(err.message || 'Something went wrong. Please try again.');
    } finally {
      setSending(false);
    }
  };

  if (submitted) {
    return (
      <div className="page-anim">
        <section style={{ borderBottom: '1px solid var(--border)' }}>
          <div className="page" style={{ padding: '40px 32px' }}>
            <SectionEyebrow id="quote" label="Submitted" />
          </div>
        </section>
        <QuoteSent form={form} receipt={receipt} />
      </div>
    );
  }

  return (
    <div className="page-anim">
      <section style={{ borderBottom: '1px solid var(--border)' }}>
        <div className="page" style={{ paddingTop: 56, paddingBottom: 40 }}>
          <SectionEyebrow id="quote" label="Quote" />
          {/* Size comes from the global h1 rule now; an inline clamp tuned for
              Geist would render far too large in the wider display face. */}
          <h1>
            Get a quote.<br/>
            <span style={{ color: 'var(--fg-faint)' }}>Tell us what you're building.</span>
          </h1>
          {mode === 'form' && (
            <div style={{ marginTop: 40 }}>
              <QuoteSteps steps={steps} current={step} />
            </div>
          )}
        </div>
      </section>

      {mode === 'choose' && (
        <QuoteChooser
          showDesigner={showDesigner}
          onShowDesigner={() => setShowDesigner(true)}
          onStartUpload={() => { setPath('upload'); setMode('form'); setStep(0); }}
        />
      )}

      {mode === 'form' && (
      <section className="section quote-body" style={{ paddingTop: 56, paddingBottom: 56, borderBottom: 'none' }}>
        <div className="page">
          {designSpec && <DesignBanner spec={designSpec} />}
          <div className="mob-stack" style={{ display: 'grid', gridTemplateColumns: '1.7fr 1fr', gap: 56 }}>
            <div>
              {isUpload ? (
                <>
                  {step === 0 && <StepProject form={form} update={update} />}
                  {step === 1 && <Step4 form={form} update={update} />}
                  {step === 2 && <Step5 form={form} update={update} />}
                </>
              ) : (
                <>
                  {step === 0 && <Step1 form={form} update={update} />}
                  {step === 1 && <Step2 form={form} update={update} />}
                  {step === 2 && <Step3 form={form} update={update} />}
                  {step === 3 && <Step4 form={form} update={update} />}
                  {step === 4 && <Step5 form={form} update={update} />}
                </>
              )}

              {sendError && (
                <div role="alert" style={{
                  marginTop: 32, padding: '14px 18px',
                  border: '1px solid var(--warn)', background: 'rgba(255,180,168,0.08)',
                }}>
                  <span className="mono" style={{ fontSize: 12, color: 'var(--warn)', letterSpacing: '0.04em' }}>
                    ⚠ {sendError}
                  </span>
                </div>
              )}

              <div className="quote-nav" style={{
                marginTop: 56, paddingTop: 24, borderTop: '1px solid var(--border)',
                display: 'flex', justifyContent: 'space-between', alignItems: 'center',
              }}>
                <button
                  className="btn"
                  disabled={sending}
                  onClick={() => {
                    if (step === 0) { setMode('choose'); setPath(null); }
                    else setStep(s => s - 1);
                  }}
                >
                  ← Back
                </button>
                <span className="mono" style={{ fontSize: 11, color: 'var(--fg-faint)', letterSpacing: '0.08em' }}>
                  STEP {step + 1} OF {steps.length}
                </span>
                {step < lastStep ? (
                  <button
                    className="btn btn-primary"
                    onClick={() => canNext() && setStep(s => s + 1)}
                    disabled={!canNext()}
                    style={{ opacity: canNext() ? 1 : 0.5 }}
                  >
                    Next
                    <svg width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M3 6 L9 6 M6 3 L9 6 L6 9" stroke="currentColor" strokeWidth="1.6"/></svg>
                  </button>
                ) : (
                  <button
                    className="btn btn-primary"
                    onClick={send}
                    disabled={!canNext() || sending}
                    style={{ opacity: canNext() && !sending ? 1 : 0.5 }}
                  >
                    {sending ? 'Sending…' : 'Submit request'}
                    {!sending && <svg width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M2 6 L5 9 L10 3" stroke="currentColor" strokeWidth="1.6"/></svg>}
                  </button>
                )}
              </div>
            </div>
            <QuoteSummary form={form} isUpload={isUpload} />
          </div>
        </div>
      </section>
      )}
    </div>
  );
};

Object.assign(window, { Quote });
