// Container Connect — shared primitives used across every page.
// Exports to window so sibling <script type="text/babel"> files can use them.

// ─────────────────────────────────────────────────────────────
// FORM SUBMISSION ROUTING
//
// Two destinations, picked automatically based on formType:
//
// 1. CC_LEAD_WEBHOOK (Make.com) — customer quote requests only.
//    The Make scenario auto-routes these to matched haulers, logs
//    them in the Leads sheet, and emails owner + customer.
//
// 2. CC_FORMSPREE_ENDPOINT (Formspree) — everything else (hauler
//    applications, contact form, waitlists). You review these
//    manually in the Formspree inbox. Volume is low enough that
//    automation isn't needed yet.
// ─────────────────────────────────────────────────────────────
const CC_LEAD_WEBHOOK       = 'https://hook.us2.make.com/3di3wq5di0hjw2rcpjaqudjqx6ze2ifk';
const CC_FORMSPREE_ENDPOINT = 'https://formspree.io/f/xwvaapky';

// Helper: routes form submission to the correct destination based on
// formType. Returns true on success, false on failure.
async function submitToFormspree({ formType, subject, data }) {
  // Customer quote → Make.com webhook (full automation pipeline)
  if (formType === 'quote-request') {
    try {
      const res = await fetch(CC_LEAD_WEBHOOK, {
        method: 'POST',
        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
        body: new URLSearchParams({ formType, subject, ...data }),
      });
      if (!res.ok) console.warn('[CC] Lead webhook returned', res.status);
      return res.ok;
    } catch (err) {
      console.error('[CC] Lead webhook error:', err);
      return false;
    }
  }

  // Hauler applications, contact form, waitlists → Formspree inbox
  if (!CC_FORMSPREE_ENDPOINT.includes('/f/') || CC_FORMSPREE_ENDPOINT.includes('REPLACE_ME')) {
    console.warn('[CC] Formspree endpoint not configured. Submission logged only:', { formType, subject, data });
    return false;
  }
  try {
    const res = await fetch(CC_FORMSPREE_ENDPOINT, {
      method: 'POST',
      headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' },
      body: JSON.stringify({ _subject: subject, formType, ...data }),
    });
    if (!res.ok) console.warn('[CC] Formspree returned', res.status);
    return res.ok;
  } catch (err) {
    console.error('[CC] Formspree error:', err);
    return false;
  }
}

const CC_AMBER = '#F5C443';
const CC_SAGE = '#7BC57F';
const CC_SAGE_FROST = '#9EE5A8';
const CC_CARD = '#0D1815';
const CC_SECTION = '#0F1F1A';
const CC_ELEV = '#112620';
const CC_BASE = '#0A1410';
const CC_BORDER = '#1F2E29';
const CC_SAGE_BORDER = 'rgba(123,197,127,0.25)';
const CC_GHOST_BORDER = 'rgba(255,255,255,0.08)';
const CC_T_PRIMARY = '#FFFFFF';
const CC_T_SECONDARY = '#A8B5B0';
const CC_T_TERTIARY = '#7A8782';
const CC_T_DISABLED = '#52605B';

const CC_SHADOW_RESTING =
  '0 0 0 1px rgba(0,0,0,0.4), 0 2px 4px rgba(0,0,0,0.3), 0 4px 8px rgba(0,0,0,0.2), inset 0 1px 0 rgba(255,255,255,0.04)';
const CC_SHADOW_ELEVATED =
  '0 0 0 1px rgba(0,0,0,0.4), 0 4px 8px rgba(0,0,0,0.25), 0 8px 16px rgba(0,0,0,0.2), 0 16px 32px rgba(0,0,0,0.15), inset 0 1px 0 rgba(255,255,255,0.05)';
const CC_AMBER_GLOW =
  '0 0 24px rgba(245,196,67,0.35), 0 4px 12px rgba(245,196,67,0.2)';
const CC_AMBER_GLOW_HOVER =
  '0 0 32px rgba(245,196,67,0.45), 0 6px 16px rgba(245,196,67,0.25)';

// ─────────────────────────────────────────────────────────────
// NEUMORPHIC TOKENS — soft, directional depth on the dark theme.
//
// On dark backgrounds true neumorphism is hard: there's no room for a
// "lighter" highlight side. Instead we use a subtle white inner highlight
// (top-left) paired with a deeper black drop (bottom-right). The result
// is tactile depth without any glow or 3D cartoonishness.
//
// Use these for: primary cards, the wizard panel, sidebar quote card,
// modal panels. Do NOT layer them onto every small element — that's
// what creates the "puffy" overdesigned feel.
// ─────────────────────────────────────────────────────────────
const CC_SHADOW_NEU =
  '-2px -2px 6px rgba(255,255,255,0.025), 6px 6px 18px rgba(0,0,0,0.45), inset 0 1px 0 rgba(255,255,255,0.04)';
const CC_SHADOW_NEU_HOVER =
  '-3px -3px 10px rgba(255,255,255,0.035), 10px 10px 28px rgba(0,0,0,0.55), inset 0 1px 0 rgba(255,255,255,0.06)';
const CC_SHADOW_NEU_PRESSED =
  'inset 4px 4px 10px rgba(0,0,0,0.55), inset -2px -2px 6px rgba(255,255,255,0.02)';

// Inset for form inputs — the "pressed into the surface" look.
const CC_SHADOW_INSET =
  'inset 3px 3px 7px rgba(0,0,0,0.45), inset -1px -1px 2px rgba(255,255,255,0.025)';
// Inset + sage focus ring (replaces the previous flat focus glow).
const CC_SHADOW_INSET_FOCUS =
  'inset 3px 3px 7px rgba(0,0,0,0.45), inset -1px -1px 2px rgba(255,255,255,0.025), 0 0 0 3px rgba(123,197,127,0.18)';

// Gradient surfaces — light from top-left feel, only ~12% delta so it
// stays subtle and doesn't fight the brand palette.
const CC_GRAD_RAISED = 'linear-gradient(145deg, #112620 0%, #0A1410 100%)';
const CC_GRAD_INSET  = 'linear-gradient(145deg, #0A1410 0%, #102119 100%)';

// --------------------------------------------------------------
// Buttons
// --------------------------------------------------------------
// ─────────────────────────────────────────────────────────────
// PRIMARY CTA — premium yellow, ambient glow, polished press.
//
// Visual stack (in order, bottom to top):
//  1. Drop shadow      → grounds the button on the page
//  2. Ambient glow     → wide, low-opacity yellow halo around it
//  3. Close glow       → tighter yellow rim (depth)
//  4. Hairline rim     → 1px yellow border-glow (sharpens the edge)
//  5. Inner top hilite → bright sliver on top edge (polish)
//  6. Inner bottom dim → subtle 1px dark line on bottom (3D feel)
//  7. Vertical gradient → lighter top → deeper bottom (subtle, ~12%)
//  8. Text             → CC_BASE (dark) + 0.3px letter-spacing
//
// Hover: brighter gradient + larger glow + 1px lift.
// Press: inverted gradient + inner shadow + 1px sink. No scaling.
// ─────────────────────────────────────────────────────────────
const CC_AMBER_GRAD       = 'linear-gradient(180deg, #FFD25A 0%, #F5C443 50%, #E8B530 100%)';
const CC_AMBER_GRAD_HOVER = 'linear-gradient(180deg, #FFE07A 0%, #FFD25A 50%, #F0BE3A 100%)';
const CC_AMBER_GRAD_PRESS = 'linear-gradient(180deg, #E8B530 0%, #F5C443 100%)';

const CC_AMBER_SHADOW =
  'inset 0 1px 0 rgba(255,232,150,0.55),' +     // top inner highlight
  ' inset 0 -1px 0 rgba(0,0,0,0.12),' +         // bottom inner shadow
  ' 0 0 0 1px rgba(245,196,67,0.25),' +         // hairline yellow rim
  ' 0 4px 14px rgba(245,196,67,0.30),' +        // close glow
  ' 0 0 32px rgba(245,196,67,0.22),' +          // ambient halo
  ' 0 2px 4px rgba(0,0,0,0.25)';                // ground shadow

const CC_AMBER_SHADOW_HOVER =
  'inset 0 1px 0 rgba(255,240,180,0.7),' +
  ' inset 0 -1px 0 rgba(0,0,0,0.12),' +
  ' 0 0 0 1px rgba(245,196,67,0.40),' +
  ' 0 6px 20px rgba(245,196,67,0.45),' +
  ' 0 0 44px rgba(245,196,67,0.35),' +
  ' 0 4px 8px rgba(0,0,0,0.25)';

const CC_AMBER_SHADOW_PRESS =
  'inset 0 3px 8px rgba(0,0,0,0.30),' +         // pushed-in inner shadow
  ' inset 0 1px 0 rgba(0,0,0,0.10),' +
  ' 0 0 0 1px rgba(245,196,67,0.30),' +
  ' 0 0 16px rgba(245,196,67,0.22),' +
  ' 0 1px 2px rgba(0,0,0,0.30)';

const CCButtonAmber = ({
  children, href, onClick, arrow = true,
  size = 'md', block = false, disabled = false, style = {},
}) => {
  const [hover, setHover] = React.useState(false);
  const [pressed, setPressed] = React.useState(false);
  const pads = size === 'sm' ? '10px 22px' : size === 'lg' ? '16px 34px' : '14px 30px';
  const fs = size === 'sm' ? 14 : size === 'lg' ? 16 : 15;

  // Disabled has no glow, no gradient, no interaction. Lives intentionally
  // flat so the active state contrast stays strong.
  const disabledStyle = {
    background: CC_BORDER,
    color: CC_T_TERTIARY,
    border: 0,
    borderRadius: 9999,
    padding: pads,
    font: `600 ${fs}px Inter, system-ui, sans-serif`,
    letterSpacing: 0.3,
    display: 'inline-flex',
    alignItems: 'center',
    justifyContent: 'center',
    gap: 8,
    cursor: 'not-allowed',
    width: block ? '100%' : 'auto',
    textDecoration: 'none',
    whiteSpace: 'nowrap',
    boxShadow: 'none',
    ...style,
  };

  const activeStyle = {
    background: pressed
      ? CC_AMBER_GRAD_PRESS
      : hover ? CC_AMBER_GRAD_HOVER : CC_AMBER_GRAD,
    color: CC_BASE,
    border: 0,
    borderRadius: 9999,
    padding: pads,
    font: `600 ${fs}px Inter, system-ui, sans-serif`,
    letterSpacing: 0.3, // slight tracking for premium feel
    display: 'inline-flex',
    alignItems: 'center',
    justifyContent: 'center',
    gap: 8,
    boxShadow: pressed
      ? CC_AMBER_SHADOW_PRESS
      : hover ? CC_AMBER_SHADOW_HOVER : CC_AMBER_SHADOW,
    cursor: 'pointer',
    transform: pressed ? 'translateY(1px)' : hover ? 'translateY(-1px)' : 'translateY(0)',
    transition:
      'background 220ms ease,' +
      ' box-shadow 220ms ease,' +
      ' transform 180ms cubic-bezier(.4,0,.2,1)',
    width: block ? '100%' : 'auto',
    textDecoration: 'none',
    whiteSpace: 'nowrap',
    ...style,
  };

  const base = disabled ? disabledStyle : activeStyle;

  const content = (
    <>
      {children}
      {arrow && <span style={{ fontSize: 16, lineHeight: 1 }}>→</span>}
    </>
  );

  const common = disabled
    ? { style: base, 'aria-disabled': true }
    : {
        onMouseEnter: () => setHover(true),
        onMouseLeave: () => { setHover(false); setPressed(false); },
        onMouseDown: () => setPressed(true),
        onMouseUp: () => setPressed(false),
        style: base,
      };

  if (disabled) {
    return <button disabled {...common}>{content}</button>;
  }
  return href
    ? <a href={href} {...common}>{content}</a>
    : <button onClick={onClick} {...common}>{content}</button>;
};

const CCButtonGhost = ({ children, href, onClick, size = 'md', style = {} }) => {
  const [hover, setHover] = React.useState(false);
  const [pressed, setPressed] = React.useState(false);
  const pads = size === 'sm' ? '10px 20px' : size === 'lg' ? '16px 32px' : '14px 28px';
  const fs = size === 'sm' ? 14 : size === 'lg' ? 16 : 15;
  const base = {
    background: hover ? CC_GRAD_RAISED : 'transparent',
    color: CC_T_PRIMARY,
    border: `1.5px solid ${hover ? 'rgba(255,255,255,0.85)' : 'rgba(255,255,255,0.55)'}`,
    borderRadius: 9999,
    padding: pads,
    font: `600 ${fs}px Inter, system-ui, sans-serif`,
    cursor: 'pointer',
    boxShadow: pressed ? CC_SHADOW_NEU_PRESSED : hover ? CC_SHADOW_NEU_HOVER : CC_SHADOW_NEU,
    transform: pressed ? 'translateY(1px) scale(0.98)' : hover ? 'translateY(-1px) scale(1.02)' : 'translateY(0)',
    transition: 'background 220ms, border-color 220ms, transform 200ms cubic-bezier(.4,0,.2,1), box-shadow 220ms',
    display: 'inline-flex',
    alignItems: 'center',
    justifyContent: 'center',
    gap: 8,
    textDecoration: 'none',
    whiteSpace: 'nowrap',
    ...style,
  };
  const common = {
    onMouseEnter: () => setHover(true),
    onMouseLeave: () => { setHover(false); setPressed(false); },
    onMouseDown: () => setPressed(true),
    onMouseUp: () => setPressed(false),
    style: base,
  };
  return href
    ? <a href={href} {...common}>{children}</a>
    : <button onClick={onClick} {...common}>{children}</button>;
};

const CCButtonSage = ({ children, href, onClick, size = 'md', block = false }) => {
  const [hover, setHover] = React.useState(false);
  const [pressed, setPressed] = React.useState(false);
  const pads = size === 'sm' ? '10px 20px' : '14px 28px';
  const fs = size === 'sm' ? 14 : 15;
  const base = {
    background: hover ? 'rgba(123,197,127,0.14)' : 'rgba(123,197,127,0.06)',
    color: CC_SAGE,
    border: `1.5px solid ${CC_SAGE_BORDER}`,
    borderRadius: 9999,
    padding: pads,
    font: `600 ${fs}px Inter, system-ui, sans-serif`,
    cursor: 'pointer',
    boxShadow: pressed ? CC_SHADOW_NEU_PRESSED : hover ? CC_SHADOW_NEU_HOVER : CC_SHADOW_NEU,
    transform: pressed ? 'translateY(1px) scale(0.98)' : hover ? 'translateY(-1px) scale(1.02)' : 'translateY(0)',
    transition: 'background 220ms, transform 200ms cubic-bezier(.4,0,.2,1), box-shadow 220ms',
    display: 'inline-flex',
    alignItems: 'center',
    justifyContent: 'center',
    gap: 8,
    textDecoration: 'none',
    width: block ? '100%' : 'auto',
    whiteSpace: 'nowrap',
  };
  const common = {
    onMouseEnter: () => setHover(true),
    onMouseLeave: () => { setHover(false); setPressed(false); },
    onMouseDown: () => setPressed(true),
    onMouseUp: () => setPressed(false),
    style: base,
  };
  return href
    ? <a href={href} {...common}>{children}</a>
    : <button onClick={onClick} {...common}>{children}</button>;
};

// --------------------------------------------------------------
// Trust pill (sage-themed)
// --------------------------------------------------------------
const CCTrustPill = ({ children, tone = 'sage', size = 'md' }) => {
  const tones = {
    sage: { c: CC_SAGE, bg: '#0F2620', bd: 'rgba(123,197,127,0.25)' },
    amber: { c: CC_AMBER, bg: '#1a1405', bd: 'rgba(245,196,67,0.3)' },
  }[tone];
  const pads = size === 'sm' ? '4px 10px' : '8px 16px';
  const fs = size === 'sm' ? 11 : 12;
  const tracking = size === 'sm' ? 1.2 : 1.4;
  return (
    <span style={{
      display: 'inline-flex', alignItems: 'center', gap: 8,
      padding: pads, borderRadius: 9999,
      background: tones.bg, border: `1px solid ${tones.bd}`,
      color: tones.c, fontFamily: 'Inter, system-ui, sans-serif',
      fontSize: fs, fontWeight: 600, letterSpacing: tracking, textTransform: 'uppercase',
    }}>
      <span style={{ width: 6, height: 6, borderRadius: '50%', background: tones.c }} />
      {children}
    </span>
  );
};

// --------------------------------------------------------------
// Brand logo (hex mark + wordmark)
// --------------------------------------------------------------
const CCLogo = ({ size = 40 }) => (
  <a href="/" style={{ display: 'flex', alignItems: 'center', gap: 12, textDecoration: 'none' }}>
    <img src="/logo-hex.png" alt="" style={{ height: size, width: size, display: 'block', objectFit: 'contain' }} />
    <img src="/logo-wordmark.png" alt="Container Connect" style={{ height: size * 0.82, display: 'block', objectFit: 'contain' }} />
  </a>
);

// --------------------------------------------------------------
// Container photos — shared across Home, Quote, Company pages.
// One source of truth so swapping any image affects every page at once.
// --------------------------------------------------------------
const CC_CONTAINER_IMG_MAP = {
  '3':  '/images/containers-green/Screenshot_2026-04-23_212230-removebg-preview.png',
  '6':  '/images/containers-green/Screenshot_2026-04-23_212242-removebg-preview.png',
  '10': '/images/containers-green/Screenshot_2026-04-23_212252-removebg-preview.png',
  '12': '/images/containers-green/Screenshot_2026-04-23_212307-removebg-preview.png',
  '15': '/images/containers-green/Screenshot_2026-04-23_212319-removebg-preview.png',
  '20': '/images/containers-green/Screenshot_2026-04-23_212336-removebg-preview.png',
  '30': '/images/containers-green/Green 30 yard.png',
  '40': '/images/containers-green/Screenshot_2026-04-23_212406-removebg-preview.png',
};

// Resolve any yard input (number, string, '10 YD³', etc.) to a photo URL.
// If the exact size has no photo, use the closest size we DO have a photo for.
function ccContainerImg(input) {
  if (!input || input === '?') return null;
  // Strip non-digits to extract numeric size: '10 YD³' → '10', 30 → '30'
  const yd = String(input).replace(/[^0-9]/g, '');
  if (!yd) return null;
  if (CC_CONTAINER_IMG_MAP[yd]) return CC_CONTAINER_IMG_MAP[yd];
  // Closest-fit fallback (e.g. 30 → 40 since we don't have a 30 photo)
  const target    = parseInt(yd, 10);
  const available = Object.keys(CC_CONTAINER_IMG_MAP).map(Number).sort((a, b) => a - b);
  const closest   = available.reduce((prev, curr) =>
    Math.abs(curr - target) <= Math.abs(prev - target) ? curr : prev
  );
  return CC_CONTAINER_IMG_MAP[String(closest)];
}

// Reusable image component. Used everywhere a dumpster photo is shown.
// Returns null for unknown sizes ('?') so caller can render a placeholder.
const CCDumpsterImg = ({ size, height = 90, style = {} }) => {
  const src = ccContainerImg(size);
  if (!src) return null;
  return (
    <img
      src={src}
      alt={`${size} yard dumpster`}
      style={{
        height,
        width: 'auto',
        maxWidth: '100%',
        objectFit: 'contain',
        objectPosition: 'bottom center',
        display: 'block',
        filter: 'drop-shadow(0 6px 10px rgba(0,0,0,0.5)) drop-shadow(0 2px 3px rgba(0,0,0,0.35))',
        ...style,
      }}
    />
  );
};

// --------------------------------------------------------------
// Form primitives
// --------------------------------------------------------------
const CCInput = ({ label, icon, name, ...props }) => {
  const [focused, setFocused] = React.useState(false);
  // Default the form-field `name` to the label so FormData captures it.
  // This is what shows up in Formspree as the key for each submitted value.
  const resolvedName = name || label;
  return (
    <label style={{ display: 'flex', flexDirection: 'column', gap: 6, minWidth: 0 }}>
      {label && <span style={{ font: '500 13px Inter, system-ui, sans-serif', color: CC_T_SECONDARY, letterSpacing: 0.2 }}>{label}</span>}
      <div style={{ position: 'relative' }}>
        {icon && (
          <span style={{ position: 'absolute', left: 16, top: '50%', transform: 'translateY(-50%)', color: CC_T_TERTIARY, pointerEvents: 'none', display: 'flex' }}>
            {icon}
          </span>
        )}
        <input
          {...props}
          name={resolvedName}
          onFocus={(e) => { setFocused(true); props.onFocus && props.onFocus(e); }}
          onBlur={(e) => { setFocused(false); props.onBlur && props.onBlur(e); }}
          style={{
            // Soft inset surface — feels pressed into the page.
            background: CC_GRAD_INSET,
            color: '#fff',
            border: `1.5px solid ${focused ? CC_SAGE : CC_BORDER}`,
            borderRadius: 12,
            padding: icon ? '14px 16px 14px 44px' : '14px 16px',
            font: '450 15px Inter, system-ui, sans-serif',
            outline: 'none',
            width: '100%',
            boxSizing: 'border-box',
            boxShadow: focused ? CC_SHADOW_INSET_FOCUS : CC_SHADOW_INSET,
            transition: 'border-color 180ms ease, box-shadow 220ms ease, background 220ms ease',
            ...(props.style || {}),
          }}
        />
      </div>
    </label>
  );
};

const CCSelect = ({ label, options, name, ...props }) => {
  const [focused, setFocused] = React.useState(false);
  const resolvedName = name || label;
  return (
    <label style={{ display: 'flex', flexDirection: 'column', gap: 6, minWidth: 0 }}>
      {label && <span style={{ font: '500 13px Inter, system-ui, sans-serif', color: CC_T_SECONDARY, letterSpacing: 0.2 }}>{label}</span>}
      <select
        {...props}
        name={resolvedName}
        onFocus={() => setFocused(true)}
        onBlur={() => setFocused(false)}
        style={{
          color: '#fff',
          border: `1.5px solid ${focused ? CC_SAGE : CC_BORDER}`,
          borderRadius: 12,
          padding: '14px 40px 14px 16px',
          font: '450 15px Inter, system-ui, sans-serif',
          outline: 'none',
          appearance: 'none',
          width: '100%',
          boxSizing: 'border-box',
          // Layer the chevron icon on top of the inset gradient.
          backgroundColor: 'transparent',
          backgroundImage: `url("data:image/svg+xml,%3Csvg width='12' height='8' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%23A8B5B0' stroke-width='2' fill='none' stroke-linecap='round'/%3E%3C/svg%3E"), linear-gradient(145deg, #0A1410 0%, #102119 100%)`,
          backgroundRepeat: 'no-repeat, no-repeat',
          backgroundPosition: 'right 16px center, 0 0',
          backgroundSize: 'auto, 100% 100%',
          boxShadow: focused ? CC_SHADOW_INSET_FOCUS : CC_SHADOW_INSET,
          transition: 'border-color 180ms ease, box-shadow 220ms ease',
        }}
      >
        {/* Force option styles — without this, the parent select's
            color:#fff cascades into the dropdown options, which the browser
            renders on a white background → white-on-white invisible text.
            Inline styles on <option> are the only cross-browser way to
            override the native dropdown's contrast. */}
        {options.map(o => (typeof o === 'string'
          ? <option key={o} value={o} style={{ background: '#0F1F1A', color: '#fff' }}>{o}</option>
          : <option key={o.value} value={o.value} style={{ background: '#0F1F1A', color: '#fff' }}>{o.label}</option>))}
      </select>
    </label>
  );
};

// --------------------------------------------------------------
// Inline icon helpers (Lucide-like, drawn as inline SVG)
// --------------------------------------------------------------
const icon = (d, { size = 20, stroke = 1.75, color = 'currentColor' } = {}) => (
  <svg width={size} height={size} viewBox="0 0 24 24" fill="none"
    stroke={color} strokeWidth={stroke} strokeLinecap="round" strokeLinejoin="round">
    {d}
  </svg>
);
const CCIcon = {
  pin: (p) => icon(<><path d="M20 10c0 7-8 13-8 13s-8-6-8-13a8 8 0 1 1 16 0Z" /><circle cx="12" cy="10" r="3" /></>, p),
  star: (p) => icon(<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" />, p),
  search: (p) => icon(<><circle cx="11" cy="11" r="7" /><path d="m21 21-4.3-4.3" /></>, p),
  check: (p) => icon(<polyline points="20 6 9 17 4 12" />, p),
  shield: (p) => icon(<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10Z" />, p),
  clock: (p) => icon(<><circle cx="12" cy="12" r="9" /><polyline points="12 7 12 12 16 14" /></>, p),
  arrowRight: (p) => icon(<><path d="M5 12h14" /><polyline points="12 5 19 12 12 19" /></>, p),
  arrowLeft: (p) => icon(<><path d="M19 12H5" /><polyline points="12 19 5 12 12 5" /></>, p),
  camera: (p) => icon(<><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z" /><circle cx="12" cy="13" r="4" /></>, p),
  upload: (p) => icon(<><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /><polyline points="17 8 12 3 7 8" /><path d="M12 3v12" /></>, p),
  dollar: (p) => icon(<><path d="M12 2v20" /><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6" /></>, p),
  truck: (p) => icon(<><path d="M1 3h15v13H1z" /><path d="M16 8h4l3 3v5h-7z" /><circle cx="5.5" cy="18.5" r="2.5" /><circle cx="18.5" cy="18.5" r="2.5" /></>, p),
  home: (p) => icon(<><path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" /><polyline points="9 22 9 12 15 12 15 22" /></>, p),
  building: (p) => icon(<><path d="M3 21V5a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v16" /><path d="M16 8h3a2 2 0 0 1 2 2v11" /><path d="M6 7h4M6 11h4M6 15h4M14 15h4" /></>, p),
  hammer: (p) => icon(<><path d="m15 12-8.5 8.5a2.12 2.12 0 0 1-3-3L12 9" /><path d="M17.64 15 22 10.64" /><path d="m20.91 11.7-1.25-1.25c-.6-.6-.93-1.4-.93-2.25v-.86L16.01 4.6a5.56 5.56 0 0 0-3.94-1.64H9l.92.82A6.18 6.18 0 0 1 12 8.4v1.56l2 2h2.47l2.26 2.26" /></>, p),
  leaf: (p) => icon(<><path d="M11 20A7 7 0 0 1 4 13c0-2 1-3.9 3-5.5s3.5-4 4-6.5c.5 2.5 2 4.9 4 6.5s3 3.5 3 5.5a7 7 0 0 1-7 7z" /><path d="M11 20v-8" /></>, p),
  plus: (p) => icon(<><path d="M12 5v14" /><path d="M5 12h14" /></>, p),
  x: (p) => icon(<><path d="M18 6 6 18" /><path d="m6 6 12 12" /></>, p),
  calendar: (p) => icon(<><rect x="3" y="4" width="18" height="18" rx="2" /><path d="M16 2v4" /><path d="M8 2v4" /><path d="M3 10h18" /></>, p),
  mail: (p) => icon(<><rect x="2" y="4" width="20" height="16" rx="2" /><path d="m22 6-10 7L2 6" /></>, p),
  phone: (p) => icon(<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.13.96.37 1.9.72 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.91.35 1.85.59 2.81.72A2 2 0 0 1 22 16.92z" />, p),
  user: (p) => icon(<><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" /><circle cx="12" cy="7" r="4" /></>, p),
  chevronR: (p) => icon(<polyline points="9 18 15 12 9 6" />, p),
  chevronD: (p) => icon(<polyline points="6 9 12 15 18 9" />, p),
  filter: (p) => icon(<polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3" />, p),
  verified: (p) => icon(<><path d="m9 12 2 2 4-4" /><path d="M21 12c0 4.97-3.58 9-8 9s-8-4.03-8-9a9 9 0 0 1 18 0Z" /></>, p),
  fire: (p) => icon(<path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z" />, p),
  quote: (p) => icon(<path d="M3 21c3-.5 4.5-1.5 6-3s2-3 2-5a6 6 0 0 0-6-6v4a2 2 0 0 0 2 2h0m10 8c3-.5 4.5-1.5 6-3s2-3 2-5a6 6 0 0 0-6-6v4a2 2 0 0 0 2 2h0" />, p),
};

// --------------------------------------------------------------
// Logo badge — renders the hauler's real logo when `logoUrl` is provided,
// falls back to a gradient + initials tile when it's not. Same component
// is used on the Company header, home featured cards, and quote results.
//
// Logo file requirements:
//   - PNG with transparent background (SVG also works)
//   - Square or close to square (we object-fit: contain so wide logos
//     just letterbox)
//   - Stored under /images/haulers/ on the workspace folder
// --------------------------------------------------------------
const CCLogoBadge = ({ name, grad, logoUrl, size = 56, radius = 14 }) => {
  const initials = name.split(' ').slice(0, 2).map(s => s[0]).join('').toUpperCase();
  const [imgFailed, setImgFailed] = React.useState(false);
  const showLogo = logoUrl && !imgFailed;

  return (
    <div style={{
      width: size, height: size, borderRadius: radius,
      // When showing a logo we use a neutral dark background so colored
      // logos read cleanly. The brand gradient only appears in the
      // initials fallback.
      background: showLogo
        ? 'linear-gradient(145deg, #0F2620 0%, #0A1410 100%)'
        : grad,
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      border: '1px solid rgba(255,255,255,0.08)',
      boxShadow: 'inset 0 1px 0 rgba(255,255,255,0.08), 0 2px 8px rgba(0,0,0,0.3)',
      flexShrink: 0,
      overflow: 'hidden',
    }}>
      {showLogo ? (
        <img
          src={logoUrl}
          alt={`${name} logo`}
          onError={() => setImgFailed(true)}
          style={{
            width: '82%', height: '82%',
            objectFit: 'contain',
            display: 'block',
          }}
        />
      ) : (
        <span style={{
          font: '700 18px Anton, Oswald, sans-serif',
          letterSpacing: 0.5,
          color: '#fff',
          textShadow: '0 1px 2px rgba(0,0,0,0.4)',
        }}>{initials}</span>
      )}
    </div>
  );
};

// ─────────────────────────────────────────────────────────────
// INVENTORY PRIORITY — operational badges that signal real customer
// benefits (faster delivery / more units available), surfaced on result
// cards and Company size grids when a hauler flags a specific size.
//
// Hard rules baked into the design:
//   1. Each value MUST tie to a real customer benefit — never just "hauler
//      featured this." Manipulation breaks trust permanently.
//   2. Priority is presentation-only. Result ranking stays price-first.
//   3. Non-priority sizes are never hidden from the customer.
//   4. Cap at 2 priority sizes per hauler (enforced manually in data.jsx
//      for v1; eventually validated server-side via partner dashboard).
// ─────────────────────────────────────────────────────────────
const CC_PRIORITY_LABELS = {
  available_today: 'Available today',
  fast_delivery:   'Fast delivery',
  most_available:  'Most available',
};

// Resolve a single size entry's priority label (or null).
function ccSizePriorityLabel(sizeEntry) {
  if (!sizeEntry || typeof sizeEntry !== 'object') return null;
  if (!sizeEntry.priority) return null;
  return CC_PRIORITY_LABELS[sizeEntry.priority] || null;
}

// Look up a hauler's priority label for a customer-requested size yard
// number. Strips non-digits so '20', '20 YD³', and '20 YD³ Lowboy' all
// resolve correctly. Returns the badge string or null.
function ccHaulerSizePriority(hauler, size) {
  if (!hauler || !size || size === '?') return null;
  const yd = parseInt(String(size).replace(/[^0-9]/g, ''), 10);
  if (!yd) return null;
  const sizes = Array.isArray(hauler.sizes) ? hauler.sizes : [];
  const match = sizes.find((s) => {
    const str = typeof s === 'string' ? s : (s && s.size) || '';
    return parseInt(String(str).replace(/[^0-9]/g, ''), 10) === yd;
  });
  return ccSizePriorityLabel(match);
}

// ─────────────────────────────────────────────────────────────
// RATING HELPERS — pick the best-available source for each hauler.
//
// Order of preference: Google Business Profile reviews (highest social
// proof, oldest, most familiar) → Container Connect-native reviews → null.
// This lets us show real stars on day one, then transition to platform
// reviews as customers start leaving them post-job.
// ─────────────────────────────────────────────────────────────
function ccHaulerRating(hauler) {
  if (!hauler) return null;
  if (hauler.googleReviewCount > 0 && typeof hauler.googleRating === 'number') {
    return { value: hauler.googleRating, source: 'google' };
  }
  if (hauler.reviews > 0 && typeof hauler.rating === 'number') {
    return { value: hauler.rating, source: 'cc' };
  }
  return null;
}

function ccHaulerReviewCount(hauler) {
  if (!hauler) return 0;
  return hauler.googleReviewCount || hauler.reviews || 0;
}

// ─────────────────────────────────────────────────────────────
// PRICING & DISTANCE HELPERS  (frontend matching/pricing engine)
//
// New picker-model architecture: the frontend filters & ranks
// haulers, customer picks ONE, then we send a single notification
// to that hauler. These helpers do the math.
// ─────────────────────────────────────────────────────────────

// In-memory cache so we don't hit zippopotam.us repeatedly per session.
const CC_ZIP_CACHE = {};

// Look up { lat, lng } for a US ZIP via free zippopotam.us API.
// Returns null on bad zip or network failure.
async function ccFetchZipLatLng(zip) {
  const z = String(zip || '').trim();
  if (!/^\d{5}$/.test(z)) return null;
  if (CC_ZIP_CACHE[z]) return CC_ZIP_CACHE[z];
  try {
    const res = await fetch(`https://api.zippopotam.us/us/${z}`);
    if (!res.ok) return null;
    const json = await res.json();
    const place = json && json.places && json.places[0];
    if (!place) return null;
    const out = {
      lat: parseFloat(place.latitude),
      lng: parseFloat(place.longitude),
      city: place['place name'],
      state: place['state abbreviation'],
    };
    CC_ZIP_CACHE[z] = out;
    return out;
  } catch (err) {
    console.warn('[CC] ZIP lookup failed for', z, err);
    return null;
  }
}

// Multiplier applied to great-circle distance to approximate driving distance.
// Real roads aren't straight lines — for LA County (grid streets + freeway
// detours) the typical "circuity factor" is ~1.35. Bumps the haversine
// straight-line value up to a more realistic drive-distance estimate.
const CC_ROAD_FACTOR = 1.35;

// Great-circle distance in miles between two {lat, lng} points.
function ccHaversineMiles(p1, p2) {
  if (!p1 || !p2) return Infinity;
  const R = 3958.8; // Earth radius in miles
  const toRad = (d) => (d * Math.PI) / 180;
  const dLat = toRad(p2.lat - p1.lat);
  const dLng = toRad(p2.lng - p1.lng);
  const a =
    Math.sin(dLat / 2) ** 2 +
    Math.cos(toRad(p1.lat)) * Math.cos(toRad(p2.lat)) * Math.sin(dLng / 2) ** 2;
  return 2 * R * Math.asin(Math.sqrt(a));
}

// Extract the yd number from a hauler size entry. Sizes can be either a plain
// string ('10 YD³') OR an object ({ size: '10 YD³', price: 295, description }).
function ccSizeEntryYd(entry) {
  const str = typeof entry === 'string' ? entry : (entry && entry.size) || '';
  return parseInt(String(str).replace(/[^0-9]/g, ''), 10);
}

// Resolve a hauler's posted price for a given size (handles "10 YD³" → 10).
function ccHaulerPriceForSize(hauler, size) {
  if (!hauler || !size || size === '?') return null;
  const yd = parseInt(String(size).replace(/[^0-9]/g, ''), 10);
  if (!yd) return null;

  // Best: explicit per-size price in the sizes array of objects.
  if (Array.isArray(hauler.sizes)) {
    const match = hauler.sizes.find((s) => ccSizeEntryYd(s) === yd);
    if (match && typeof match === 'object' && typeof match.price === 'number') {
      return match.price;
    }
  }
  // Next: explicit pricing table { '10': 295, '20': 425, ... }
  if (hauler.pricing && hauler.pricing[String(yd)] != null) {
    return hauler.pricing[String(yd)];
  }
  // Fallback: priceFrom (entry-level price in posted catalog)
  if (typeof hauler.priceFrom === 'number') {
    // Crude size scaling so larger sizes cost more than the smallest one.
    // Replace once haulers send real price tables.
    const sizes = (hauler.sizes || [])
      .map(ccSizeEntryYd)
      .filter(Boolean)
      .sort((a, b) => a - b);
    const minYd = sizes[0] || 10;
    const maxYd = sizes[sizes.length - 1] || 40;
    if (yd <= minYd) return hauler.priceFrom;
    // Linear scale from priceFrom (at min size) up to ~2x at max size.
    const ratio = (yd - minYd) / Math.max(1, maxYd - minYd);
    return Math.round(hauler.priceFrom * (1 + ratio));
  }
  return null;
}

// Compute total estimated price for one customer × hauler combo.
// distance: miles from hauler.dispatchZip to customer.zip
// Returns { total, breakdown: { base, distSurcharge, materialFee, distance } }
function ccEstimatePrice({ hauler, distance, size, materialType }) {
  const base = ccHaulerPriceForSize(hauler, size);
  if (base == null) return null;

  const freeMiles = typeof hauler.freeMiles === 'number' ? hauler.freeMiles : 15;
  const perMile  = typeof hauler.pricePerMileOver === 'number' ? hauler.pricePerMileOver : 5;
  const overage  = Math.max(0, (distance || 0) - freeMiles);
  const distSurcharge = Math.round(overage * perMile);

  const fees = hauler.materialFees || {};
  const materialFee = typeof fees[materialType] === 'number' ? fees[materialType] : 0;

  return {
    total: base + distSurcharge + materialFee,
    breakdown: { base, distSurcharge, materialFee, distance: Math.round((distance || 0) * 10) / 10 },
  };
}

// Filter & rank haulers for a given customer request.
// Returns sorted array of { hauler, distance, eligible, price, reason, availableLater }
// where price is the result of ccEstimatePrice (or null if not priced).
// `availableLater` is set to a YYYY-MM-DD string when the hauler can't deliver
// by the requested date but COULD a few days later — card shows "Earliest
// available: X" so the customer can decide whether to wait.
function ccFindEligibleHaulers({ haulers, customerLatLng, size, materialType, requestedDate }) {
  if (!haulers || !haulers.length) return [];

  const yd = size && size !== '?'
    ? parseInt(String(size).replace(/[^0-9]/g, ''), 10)
    : null;

  const ranked = haulers.map((h) => {
    if (h.available === false) {
      return { hauler: h, distance: null, eligible: false, price: null, reason: 'unavailable' };
    }
    if (!h.lat || !h.lng) {
      return { hauler: h, distance: null, eligible: false, price: null, reason: 'no-location' };
    }
    if (!customerLatLng) {
      return { hauler: h, distance: null, eligible: false, price: null, reason: 'no-customer-location' };
    }
    // Approximate drive distance — haversine gives straight-line, so we
    // bump it by the road factor to reflect LA County's grid + freeway routing.
    const distance = ccHaversineMiles(customerLatLng, { lat: h.lat, lng: h.lng }) * CC_ROAD_FACTOR;
    const radius = typeof h.deliveryRadiusMiles === 'number' ? h.deliveryRadiusMiles : 25;
    if (distance > radius) {
      return { hauler: h, distance, eligible: false, price: null, reason: 'out-of-range' };
    }

    // Size availability check (if hauler has a sizes list)
    if (yd && Array.isArray(h.sizes) && h.sizes.length) {
      const haulerYds = h.sizes.map(ccSizeEntryYd).filter(Boolean);
      // Allow ±5 yd flexibility (e.g. customer wants 12, hauler has 10/15)
      const hasMatch = haulerYds.some((y) => Math.abs(y - yd) <= 5);
      if (!hasMatch) {
        return { hauler: h, distance, eligible: false, price: null, reason: 'size-unavailable' };
      }
    }

    // Date availability — only checked when the customer specified a date.
    // Date strings are YYYY-MM-DD so lexical comparison works correctly.
    let availableLater = null;
    if (requestedDate) {
      const blackouts = Array.isArray(h.blackoutDates) ? h.blackoutDates : [];
      if (blackouts.includes(requestedDate)) {
        return { hauler: h, distance, eligible: false, price: null, reason: 'blacked-out' };
      }
      if (h.nextAvailableDate && requestedDate < h.nextAvailableDate) {
        // Hauler can't make the requested date, but COULD on a later one.
        // Still show them — let the customer decide whether to wait.
        availableLater = h.nextAvailableDate;
      }
    }

    const price = ccEstimatePrice({ hauler: h, distance, size, materialType });
    return { hauler: h, distance, eligible: true, price, reason: null, availableLater };
  });

  // Sort: eligible first, then haulers who can deliver on the requested date
  // before haulers who'd ask the customer to wait, then by price, then distance.
  return ranked.sort((a, b) => {
    if (a.eligible !== b.eligible) return a.eligible ? -1 : 1;
    const aLate = a.availableLater ? 1 : 0;
    const bLate = b.availableLater ? 1 : 0;
    if (aLate !== bLate) return aLate - bLate;
    const ap = a.price ? a.price.total : Infinity;
    const bp = b.price ? b.price.total : Infinity;
    if (ap !== bp) return ap - bp;
    return (a.distance || Infinity) - (b.distance || Infinity);
  });
}

// ─────────────────────────────────────────────────────────────
// CCIconTile — reusable neumorphic icon container.
//
// Three states:
//   default → subtle raised tile, muted icon
//   hover   → lifts 1px, shadow deepens, icon brightens to sage frost
//   active  → inset (pressed) shadow + sage tint, icon in full sage
//
// Use anywhere we previously had a flat <span> wrapping an icon —
// project-type cards, feature blocks, step icons, callout pills.
// Pass `icon` as a function (e.g. `CCIcon.home`) — not pre-rendered.
// ─────────────────────────────────────────────────────────────
const CC_TILE_GRAD       = 'linear-gradient(145deg, #142A23 0%, #0A1410 100%)';
const CC_TILE_GRAD_ACTIVE = 'linear-gradient(145deg, rgba(123,197,127,0.18) 0%, rgba(123,197,127,0.06) 100%)';

const CC_TILE_SHADOW =
  'inset 0 1px 0 rgba(255,255,255,0.06),' +
  ' -1px -1px 3px rgba(255,255,255,0.025),' +
  ' 2px 2px 6px rgba(0,0,0,0.4)';
const CC_TILE_SHADOW_HOVER =
  'inset 0 1px 0 rgba(255,255,255,0.08),' +
  ' -1.5px -1.5px 5px rgba(255,255,255,0.04),' +
  ' 3px 3px 10px rgba(0,0,0,0.5)';
const CC_TILE_SHADOW_ACTIVE =
  'inset 2px 2px 5px rgba(0,0,0,0.4),' +
  ' inset -1px -1px 2px rgba(255,255,255,0.025),' +
  ' 0 0 0 1px rgba(123,197,127,0.25),' +
  ' 0 0 14px rgba(123,197,127,0.15)';

const CCIconTile = ({
  icon, active = false, hover = false,
  size = 40, iconSize = 20, style = {},
}) => {
  const iconColor = active ? CC_SAGE : (hover ? CC_SAGE_FROST : '#9EAEA9');
  const radius = size <= 32 ? 8 : size >= 56 ? 14 : 12;
  return (
    <span style={{
      width: size, height: size, borderRadius: radius,
      background: active ? CC_TILE_GRAD_ACTIVE : CC_TILE_GRAD,
      boxShadow: active
        ? CC_TILE_SHADOW_ACTIVE
        : (hover ? CC_TILE_SHADOW_HOVER : CC_TILE_SHADOW),
      border: '1px solid rgba(255,255,255,0.04)',
      display: 'inline-flex',
      alignItems: 'center',
      justifyContent: 'center',
      color: iconColor,
      flexShrink: 0,
      transform: !active && hover ? 'translateY(-1px)' : 'translateY(0)',
      transition: 'transform 220ms cubic-bezier(.4,0,.2,1), box-shadow 220ms ease, background 220ms ease',
      ...style,
    }}>
      {icon({ size: iconSize, color: iconColor, stroke: 1.75 })}
    </span>
  );
};

Object.assign(window, {
  CC_AMBER, CC_SAGE, CC_SAGE_FROST, CC_CARD, CC_SECTION, CC_ELEV, CC_BASE,
  CC_BORDER, CC_SAGE_BORDER, CC_GHOST_BORDER,
  CC_T_PRIMARY, CC_T_SECONDARY, CC_T_TERTIARY, CC_T_DISABLED,
  CC_SHADOW_RESTING, CC_SHADOW_ELEVATED, CC_AMBER_GLOW, CC_AMBER_GLOW_HOVER,
  CC_SHADOW_NEU, CC_SHADOW_NEU_HOVER, CC_SHADOW_NEU_PRESSED,
  CC_SHADOW_INSET, CC_SHADOW_INSET_FOCUS,
  CC_GRAD_RAISED, CC_GRAD_INSET,
  CCButtonAmber, CCButtonGhost, CCButtonSage,
  CCTrustPill, CCLogo, CCInput, CCSelect, CCIcon, CCLogoBadge, CCIconTile,
  CCDumpsterImg, ccContainerImg, CC_CONTAINER_IMG_MAP,
  submitToFormspree, CC_FORMSPREE_ENDPOINT, CC_LEAD_WEBHOOK,
  ccFetchZipLatLng, ccHaversineMiles, ccEstimatePrice, ccFindEligibleHaulers,
  ccHaulerPriceForSize,
  ccHaulerRating, ccHaulerReviewCount,
  CC_PRIORITY_LABELS, ccSizePriorityLabel, ccHaulerSizePriority,
});
