/* global React, ReactDOM */
const { useState, useEffect, useRef, useMemo } = React;

// ----- palette (18 colors = the actual filament inventory; one list reused
//        for every swatch row so the user can pick any colour for any part) -----
const PALETTE = [
  { name: "Ivory White",     hex: "#FFFFFF", text: "#444"    },
  { name: "Sakura Pink",     hex: "#E8AFCF", text: "#5a2030" },
  { name: "Caramel",         hex: "#AE835B", text: "#fff"    },
  { name: "Lilac Purple",    hex: "#AE96D4", text: "#fff"    },
  { name: "Mandarin Orange", hex: "#F99963", text: "#5a2010" },
  { name: "Lemon Yellow",    hex: "#F7D959", text: "#5a4500" },
  { name: "Plum",            hex: "#950051", text: "#fff"    },
  { name: "Scarlet Red",     hex: "#DE4343", text: "#fff"    },
  { name: "Dark Green",      hex: "#68724D", text: "#fff"    },
  { name: "Grass Green",     hex: "#61C680", text: "#fff"    },
  { name: "Dark Blue",       hex: "#042F56", text: "#fff"    },
  { name: "Sky Blue",        hex: "#56B7E6", text: "#1f3a55" },
  { name: "Marine Blue",     hex: "#0078BF", text: "#fff"    },
  { name: "Nardo Gray",      hex: "#757575", text: "#fff"    },
  { name: "Charcoal",        hex: "#000000", text: "#fff"    },
  { name: "Desert Tan",      hex: "#E8DBB7", text: "#5a4520" },
  { name: "Latte Brown",     hex: "#D3B7A7", text: "#5a4520" },
  { name: "Dark Chocolate",  hex: "#4D3324", text: "#fff"    },
];
const BASE_COLORS   = PALETTE;
const KEY_COLORS    = PALETTE;
const LETTER_COLORS = PALETTE;
const CHAIN_COLORS  = PALETTE;

// Pricing (RON):
//  - Base 15 (or 20 when "Personalizare avansată" is open).
//  - Letters 1–3 are included; from letter 4 onward, +0.50 each. Spaces count.
const PRICE = { base: 28.70, basePerLetter: 33.70, includedLetters: 3, perExtraLetter: 1.50 };
function calcPrice(text, customOpen) {
  const base = customOpen ? PRICE.basePerLetter : PRICE.base;
  const extra = Math.max(0, text.length - PRICE.includedLetters);
  return base + extra * PRICE.perExtraLetter;
}

const SURPRISE_WORDS = ["BUN", "HEI", "WOW", "POP", "OK", "TOP", "MERSI", "CIAO", "BUM", "PUF", "M&M", "BITCOIN", "ANA&ADI", "GLOW"];
const pickSurprise = () => SURPRISE_WORDS[Math.floor(Math.random() * SURPRISE_WORDS.length)];

// ---------- color helpers ----------
function shade(hex, percent) {
  const n = hex.replace("#", "");
  const num = parseInt(n.length === 3 ? n.split("").map((c) => c + c).join("") : n, 16);
  let r = (num >> 16) & 0xff, g = (num >> 8) & 0xff, b = num & 0xff;
  const t = percent < 0 ? 0 : 255;
  const p = Math.abs(percent) / 100;
  r = Math.round((t - r) * p + r);
  g = Math.round((t - g) * p + g);
  b = Math.round((t - b) * p + b);
  return `#${(0x1000000 + r * 0x10000 + g * 0x100 + b).toString(16).slice(1)}`;
}

// ---------- letter STL mapping ----------
// Allowed input: A-Z, & and space. Space is rendered as a blank cap.
const LETTER_URL = (ch) => {
  if (ch === " ") return null;
  if (ch === "&") return "models/letters/AMP.stl";
  return `models/letters/${ch}.stl`;
};
const ALLOWED_RE = /[^A-Z& ]/g;
const MAX_LEN = 10;

// ---------- preview: full keychain row ----------
function Keyboard3D({ accent, baseColor, keyColor, letterColor, text, perLetter, customOpen, soundOn }) {
  // When per-letter customization is active AND populated, use those colors;
  // otherwise fall back to the global keyColor / letterColor.
  const letters = React.useMemo(
    () => text.split("").map((ch, i) => {
      // When the dropdown is open, per-letter overrides apply.
      // When closed, globals win — the user expects toggling closed and
      // changing Keycaps/Letters globally to overwrite per-letter.
      const pl = customOpen && perLetter && perLetter[i];
      return {
        url: LETTER_URL(ch),
        color: (pl && pl.letter) || letterColor,
        capColor: (pl && pl.cap) || keyColor,
      };
    }),
    [text, letterColor, keyColor, perLetter, customOpen]
  );
  const cap = React.useMemo(
    () => ({ url: "models/keycap.stl", color: keyColor }),
    [keyColor]
  );
  const baseProp = React.useMemo(
    () => ({ url: "models/base_4.stl", color: baseColor, designedFor: 4 }),
    [baseColor]
  );
  const [pressedOnce, setPressedOnce] = React.useState(false);
  const handleCapPress = React.useCallback(() => {
    if (soundOn) clickSound();
    setPressedOnce(true);
  }, [soundOn]);
  return (
    <div className="kb-stage">
      <div className="kb-platter" style={{ background: `radial-gradient(ellipse at center, ${accent}22, transparent 60%)` }}/>
      <window.ModelViewer cap={cap} letters={letters} base={baseProp} onCapPress={handleCapPress} />
      <div className={`cf-hint-bubble ${pressedOnce ? "is-hidden" : ""}`} aria-hidden="true">
        Apasă o tastă <span className="cf-hint-icon">🔊</span>
      </div>
      <div className="kb-hint">drag to rotate · scroll to zoom · click a key</div>
    </div>
  );
}

// ---------- swatch row ----------
function SwatchRow({ label, options, value, onChange, disabled, disabledHint }) {
  return (
    <div className={`cf-section ${disabled ? "is-disabled" : ""}`}>
      <div className="cf-row-head">
        <span className="cf-label">{label}</span>
        <span className="cf-value">
          {disabled && disabledHint ? disabledHint : options.find((o) => o.hex === value)?.name}
        </span>
      </div>
      <div className="cf-swatches">
        {options.map((o) => (
          <button
            key={o.hex}
            className={`cf-swatch ${value === o.hex ? "is-on" : ""}`}
            style={{ background: o.hex }}
            onClick={() => !disabled && onChange(o.hex)}
            disabled={disabled}
            title={o.name}
            aria-label={o.name}
          >
            {value === o.hex && (
              <svg width="14" height="14" viewBox="0 0 14 14" fill="none">
                <path d="M3 7.5L6 10.5L11 4.5" stroke={o.text} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
              </svg>
            )}
          </button>
        ))}
      </div>
    </div>
  );
}

// ---------- per-letter dropdown ----------
function CustomDropdown({ open, onToggle, text, perLetter, globalKey, globalLetter, onSetCap, onSetLetter }) {
  const contentRef = useRef(null);
  // Smooth height animation: when opening, set max-height to scrollHeight; when closing, set to 0.
  // We measure on each render using a ref + a useEffect.
  const [maxH, setMaxH] = useState(0);
  useEffect(() => {
    if (!contentRef.current) return;
    if (open) {
      // Defer to next frame so DOM is laid out, then measure.
      const id = requestAnimationFrame(() => {
        if (contentRef.current) setMaxH(contentRef.current.scrollHeight);
      });
      return () => cancelAnimationFrame(id);
    }
    setMaxH(0);
  }, [open, text, perLetter]);

  const chars = text.split("");

  return (
    <div className={`cf-section cf-custom ${open ? "is-open" : ""}`}>
      <button
        type="button"
        className="cf-custom-toggle"
        onClick={onToggle}
        aria-expanded={open}
      >
        <span className="cf-custom-title">
          <span className="cf-custom-spark" aria-hidden="true">
            <svg width="14" height="14" viewBox="0 0 14 14" fill="none">
              <path d="M7 1l1.4 4.2L12.6 7 8.4 8.4 7 12.6 5.6 8.4 1.4 7 5.6 5.6z" fill="currentColor"/>
            </svg>
          </span>
          <span>Personalizare avansată</span>
        </span>
        <span className="cf-custom-meta">
          <span className="cf-custom-sub">
            {chars.length === 0
              ? "Scrie ceva mai întâi"
              : open
                ? "Culori per literă"
                : `${chars.length} ${chars.length === 1 ? "literă" : "litere"}`}
          </span>
          <svg className="cf-custom-caret" width="14" height="14" viewBox="0 0 14 14" fill="none">
            <path d="M3.5 5.5L7 9l3.5-3.5" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"/>
          </svg>
        </span>
      </button>
      <div
        className="cf-custom-body"
        style={{ maxHeight: maxH }}
        aria-hidden={!open}
      >
        <div ref={contentRef} className="cf-custom-inner">
          {chars.length === 0 && (
            <div className="cf-custom-empty">Adaugă caractere în „Textul Tău” pentru a le personaliza individual.</div>
          )}
          {chars.map((ch, i) => {
            const entry = perLetter[i] || { cap: globalKey, letter: globalLetter };
            const display = ch === " " ? "␣" : ch;
            const isSpace = ch === " ";
            return (
              <div key={i} className="cf-letter-card">
                <div className="cf-letter-head">
                  <div className="cf-letter-glyph" style={{ background: entry.cap, color: entry.letter }}>
                    {display}
                  </div>
                  <div className="cf-letter-meta">
                    <div className="cf-letter-pos">Poziția {i + 1}</div>
                    <div className="cf-letter-name">{isSpace ? "Spațiu" : `Litera „${ch}”`}</div>
                  </div>
                </div>

                <div className="cf-letter-row">
                  <div className="cf-letter-row-label">
                    <span className="cf-letter-row-dot" style={{ background: entry.cap }}/>
                    <span>Tastă</span>
                  </div>
                  <div className="cf-letter-swatches">
                    {KEY_COLORS.map((o) => (
                      <button
                        key={o.hex}
                        className={`cf-mini-swatch ${entry.cap === o.hex ? "is-on" : ""}`}
                        style={{ background: o.hex }}
                        onClick={() => onSetCap(i, o.hex)}
                        title={o.name}
                        aria-label={`${o.name} tastă`}
                      >
                        {entry.cap === o.hex && (
                          <svg width="10" height="10" viewBox="0 0 14 14" fill="none">
                            <path d="M3 7.5L6 10.5L11 4.5" stroke={o.text} strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round"/>
                          </svg>
                        )}
                      </button>
                    ))}
                  </div>
                </div>

                {!isSpace && (
                  <div className="cf-letter-row">
                    <div className="cf-letter-row-label">
                      <span className="cf-letter-row-dot" style={{ background: entry.letter }}/>
                      <span>Literă</span>
                    </div>
                    <div className="cf-letter-swatches">
                      {LETTER_COLORS.map((o) => (
                        <button
                          key={o.hex}
                          className={`cf-mini-swatch ${entry.letter === o.hex ? "is-on" : ""}`}
                          style={{ background: o.hex }}
                          onClick={() => onSetLetter(i, o.hex)}
                          title={o.name}
                          aria-label={`${o.name} literă`}
                        >
                          {entry.letter === o.hex && (
                            <svg width="10" height="10" viewBox="0 0 14 14" fill="none">
                              <path d="M3 7.5L6 10.5L11 4.5" stroke={o.text} strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round"/>
                            </svg>
                          )}
                        </button>
                      ))}
                    </div>
                  </div>
                )}
              </div>
            );
          })}
        </div>
      </div>
    </div>
  );
}

// ---------- click sound — sharp, mechanical ----------
let audioCtx = null;
function clickSound() {
  try {
    if (!audioCtx) audioCtx = new (window.AudioContext || window.webkitAudioContext)();
    const ctx = audioCtx;
    const sr = ctx.sampleRate;
    const now = ctx.currentTime;

    // 1) High, very short click transient (the "tick")
    const burstLen = Math.floor(sr * 0.012); // 12ms — extremely brief
    const buf = ctx.createBuffer(1, burstLen, sr);
    const data = buf.getChannelData(0);
    for (let i = 0; i < burstLen; i++) {
      const env = Math.pow(1 - i / burstLen, 2.2);
      data[i] = (Math.random() * 2 - 1) * env;
    }
    const src = ctx.createBufferSource();
    src.buffer = buf;
    const bp = ctx.createBiquadFilter();
    bp.type = "bandpass";
    bp.frequency.value = 5500 + Math.random() * 800;
    bp.Q.value = 4.5;
    const hp = ctx.createBiquadFilter();
    hp.type = "highpass";
    hp.frequency.value = 2500;
    const ng = ctx.createGain();
    ng.gain.value = 0.85;
    src.connect(bp).connect(hp).connect(ng).connect(ctx.destination);
    src.start(now);

    // 2) Pitched "tink" pulse — very short sine that gives the click a tonal character
    const o = ctx.createOscillator();
    const og = ctx.createGain();
    o.type = "sine";
    o.frequency.setValueAtTime(3800 + Math.random() * 400, now);
    o.frequency.exponentialRampToValueAtTime(2200, now + 0.012);
    og.gain.setValueAtTime(0.0001, now);
    og.gain.exponentialRampToValueAtTime(0.18, now + 0.001);
    og.gain.exponentialRampToValueAtTime(0.0001, now + 0.018);
    o.connect(og).connect(ctx.destination);
    o.start(now);
    o.stop(now + 0.022);

    // 3) Bottom-out tick 8ms later (the "clack")
    const t2 = now + 0.008;
    const buf2 = ctx.createBuffer(1, Math.floor(sr * 0.008), sr);
    const d2 = buf2.getChannelData(0);
    for (let i = 0; i < d2.length; i++) {
      d2[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / d2.length, 2);
    }
    const src2 = ctx.createBufferSource();
    src2.buffer = buf2;
    const bp2 = ctx.createBiquadFilter();
    bp2.type = "bandpass";
    bp2.frequency.value = 7500;
    bp2.Q.value = 6;
    const ng2 = ctx.createGain();
    ng2.gain.value = 0.6;
    src2.connect(bp2).connect(ng2).connect(ctx.destination);
    src2.start(t2);
  } catch (e) {}
}

// ---------- main app ----------
const STATE_KEY = "clickaboo_keys:state";
function loadSaved() {
  try { return JSON.parse(sessionStorage.getItem(STATE_KEY)) || {}; } catch (e) { return {}; }
}

function App() {
  const tweaksDefaults = /*EDITMODE-BEGIN*/{
    "accent": "#3D6BFF"
  }/*EDITMODE-END*/;
  const [tweaks, setTweak] = window.useTweaks(tweaksDefaults);

  const saved = loadSaved();
  const hasSaved = saved && typeof saved.text === "string";
  const [text, setText] = useState(() => hasSaved ? saved.text : pickSurprise());
  // Sanitize starting text in case of stale state.
  useEffect(() => { setText((t) => t.toUpperCase().replace(ALLOWED_RE, "").slice(0, MAX_LEN)); }, []);
  const [base, setBase]     = useState(() => saved.base   || "#F5D24A");
  const [keyC, setKeyC]     = useState(() => saved.keyC   || "#B89BE8");
  const [letter, setLetter] = useState(() => saved.letter || "#FFB8D0");
  const [chain, setChain]   = useState(() => saved.chain  || "#8A8A4A");
  const [soundOn, setSoundOn] = useState(() => typeof saved.soundOn === "boolean" ? saved.soundOn : true);

  // ----- Per-letter customization -----
  // perLetter is an array index-aligned with `text`. Each entry: { cap, letter }.
  // Inherit from current globals when a new slot appears (default_init = inherit).
  const [customOpen, setCustomOpen] = useState(() => !!saved.customOpen);
  const [perLetter, setPerLetter] = useState(() => Array.isArray(saved.perLetter) ? saved.perLetter : []);
  // Sync perLetter length to text length, preserving existing entries and
  // initializing new ones with the current global keyC / letter colors.
  useEffect(() => {
    setPerLetter((prev) => {
      const next = [];
      for (let i = 0; i < text.length; i++) {
        next.push(prev[i] || { cap: keyC, letter: letter });
      }
      return next;
    });
    // Intentionally only depends on text.length so we don't reset entries when
    // the user changes individual characters; new appended slots inherit
    // whatever the globals are at that moment.
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [text.length]);

  useEffect(() => {
    try {
      sessionStorage.setItem(STATE_KEY, JSON.stringify({
        text, base, keyC, letter, chain, soundOn, customOpen, perLetter,
      }));
    } catch (e) {}
  }, [text, base, keyC, letter, chain, soundOn, customOpen, perLetter]);

  const setLetterCap = (i, hex) => {
    setPerLetter((prev) => {
      const next = prev.slice();
      next[i] = { ...(next[i] || { cap: keyC, letter: letter }), cap: hex };
      return next;
    });
  };
  const setLetterLetter = (i, hex) => {
    setPerLetter((prev) => {
      const next = prev.slice();
      next[i] = { ...(next[i] || { cap: keyC, letter: letter }), letter: hex };
      return next;
    });
  };

  const length = Math.max(1, Math.min(MAX_LEN, text.length || 1));
  const price = calcPrice(text, customOpen);

  const surprise = () => {
    const pick = (arr) => arr[Math.floor(Math.random() * arr.length)];
    setBase(pick(BASE_COLORS).hex);
    setKeyC(pick(KEY_COLORS).hex);
    setLetter(pick(LETTER_COLORS).hex);
    setChain(pick(CHAIN_COLORS).hex);
    setText(pickSurprise());
    // Clear per-letter overrides on surprise so the new global colors apply cleanly.
    setPerLetter([]);
    setCustomOpen(false);
    if (soundOn) clickSound();
  };

  const handleTextChange = (v) => {
    const cleaned = v.toUpperCase().replace(ALLOWED_RE, "").slice(0, MAX_LEN);
    setText(cleaned);
  };

  const accent = tweaks.accent;

  return (
    <div className="cf-app" style={{ "--accent": accent }}>
      <aside className="cf-left">
        <header className="cf-header">
          <div className="cf-brand">
            <a href="/#hero" aria-label="Înapoi la Clickaboo" className="cf-brand-link">
              <img src="/assets/logo.svg" alt="Clickaboo" className="cf-brand-logo" />
            </a>
          </div>
          <div className="cf-step">Personalizează</div>
        </header>

        <div className="cf-scroll">
          <div className="cf-section">
            <div className="cf-row-head">
              <span className="cf-label">Textul Tău</span>
              <span className="cf-value">{text.length}/10</span>
            </div>
            <input
              className="cf-input"
              value={text}
              onChange={(e) => handleTextChange(e.target.value)}
              maxLength={MAX_LEN}
              placeholder="SCRIE…"
              spellCheck={false}
            />
            <div className="cf-hint">Litere A–Z, „&” și spațiu · lungimea este automată</div>
          </div>

          <div className="cf-section">
            <div className="cf-row-head">
              <span className="cf-label">Lungime</span>
              <span className="cf-value">{length} {length === 1 ? "tastă" : "taste"}</span>
            </div>
            <div className="cf-len-bar">
              {Array.from({ length: 10 }).map((_, i) => (
                <div key={i} className={`cf-len-cell ${i < length ? "is-on" : ""}`} style={{ background: i < length ? accent : undefined }}/>
              ))}
            </div>
          </div>

          <div className="cf-section">
            <div className="cf-extras">
              <button className="cf-extra-btn" onClick={surprise}>
                <svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M2 4h3l2 4 2-4h3M2 12h3l2-4M11 12h3" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round"/><circle cx="13.5" cy="3" r="1" fill="currentColor"/><circle cx="3" cy="14" r="1" fill="currentColor"/></svg>
                Surprinde-mă
              </button>
              <button
                className={`cf-extra-btn ${soundOn ? "is-on" : ""}`}
                onClick={() => { setSoundOn(!soundOn); if (!soundOn) clickSound(); }}
              >
                {soundOn ? (
                  <svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M3 6v4h2l3 3V3L5 6H3zM10.5 5.5a3 3 0 010 5M12.5 3.5a6 6 0 010 9" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"/></svg>
                ) : (
                  <svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M3 6v4h2l3 3V3L5 6H3zM11 6l4 4M15 6l-4 4" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"/></svg>
                )}
                Sunet click {soundOn ? "pornit" : "oprit"}
              </button>
            </div>
          </div>

          <SwatchRow label="Bază" options={BASE_COLORS} value={base} onChange={setBase} />

          <SwatchRow
            label="Taste"
            options={KEY_COLORS}
            value={keyC}
            onChange={setKeyC}
            disabled={customOpen}
            disabledHint="Controlat per-literă mai jos"
          />
          <SwatchRow
            label="Litere"
            options={LETTER_COLORS}
            value={letter}
            onChange={setLetter}
            disabled={customOpen}
            disabledHint="Controlat per-literă mai jos"
          />

          <CustomDropdown
            open={customOpen}
            onToggle={() => setCustomOpen((v) => !v)}
            text={text}
            perLetter={perLetter}
            globalKey={keyC}
            globalLetter={letter}
            onSetCap={setLetterCap}
            onSetLetter={setLetterLetter}
          />
        </div>

        <footer className="cf-footer">
          <div className="cf-price-row">
            <div>
              <div className="cf-price-label">Total</div>
              <div className="cf-price-sub">include printare & breloc</div>
            </div>
            <div className="cf-price">{price.toFixed(2)} RON</div>
          </div>
          <button className="cf-checkout" style={{ background: accent }} onClick={() => {
            clickSound();
            const params = new URLSearchParams({ from: "clickaboo_keys", price: price.toFixed(2) });
            window.location.href = `/keychains/?${params}`;
          }}>
            Urmatorul Pas
            <svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M3 8h10M9 4l4 4-4 4" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"/></svg>
          </button>
        </footer>
      </aside>

      <main className="cf-right">
        <div className="cf-stage-meta">
          <div className="cf-stage-title">
            <span className="cf-stage-label">Previzualizare</span>
            <span className="cf-stage-sku">CK-{length}{text ? "-" + text : ""}</span>
          </div>
        </div>
        <Keyboard3D accent={accent} baseColor={base} keyColor={keyC} letterColor={letter} text={text || "A"} perLetter={perLetter} customOpen={customOpen} soundOn={soundOn} />
        <div className="cf-legend">
          <Chip color={base} label="Bază" />
          <Chip color={keyC} label="Taste" />
          <Chip color={letter} label="Litere" />
        </div>
      </main>

      <window.TweaksPanel title="Tweaks">
        <window.TweakSection title="UI Accent">
          <window.TweakColor
            label="Accent"
            value={tweaks.accent}
            onChange={(v) => setTweak("accent", v)}
            options={["#3D6BFF", "#E85A4F", "#1A1A1E", "#40B080"]}
          />
        </window.TweakSection>
      </window.TweaksPanel>
    </div>
  );
}

function Chip({ color, label }) {
  return (
    <div className="cf-chip">
      <span className="cf-chip-dot" style={{ background: color }}/>
      <span>{label}</span>
    </div>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
