/* Match & Wood — Taxonomy Generator UI (React). Logic from MW_SCHEMA. */
const { useState, useEffect, useRef, useCallback } = React;

const S = window.MW_SCHEMA;
const COUNTRIES = window.MW_COUNTRIES || [];
const PLATFORMS = Object.keys(S.SCHEMA);

/* ---- small helpers ---- */
function cleanVal(v) { return (v || '').trim().replace(/\s+/g, ' '); }

/* ===================================================================== */
/* Segmented platform control                                            */
/* ===================================================================== */
function PlatformStep({ platform, onPick }) {
  return (
    <div className="step">
      <div className="step-head">
        <span className="step-num">01</span>
        <span className="step-title">Platform</span>
        <span className="step-hint">Pick where the campaign lives</span>
      </div>
      <div className="segmented">
        {PLATFORMS.map((p) => (
          <button key={p} className="seg" aria-pressed={platform === p}
                  onClick={() => onPick(p)}>
            <span>{S.SCHEMA[p].label}</span>
            <span className="seg-blurb">{S.SCHEMA[p].blurb}</span>
          </button>
        ))}
      </div>
    </div>
  );
}

/* ===================================================================== */
/* Level step                                                            */
/* ===================================================================== */
function LevelStep({ platform, level, onPick }) {
  const disabled = !platform;
  const levels = platform ? Object.keys(S.SCHEMA[platform].levels) : [];
  return (
    <div className={'step' + (disabled ? ' disabled' : '')}>
      <div className="step-head">
        <span className="step-num">02</span>
        <span className="step-title">Naming level</span>
        {platform && <span className="step-hint">{levels.length} levels for {platform}</span>}
      </div>
      <div className="level-row">
        {levels.map((lv) => (
          <button key={lv} className="lvl" aria-pressed={level === lv}
                  onClick={() => onPick(lv)}>{lv}</button>
        ))}
      </div>
    </div>
  );
}

/* ===================================================================== */
/* Geo builder                                                           */
/* ===================================================================== */
/* Single mode: exactly ONE geo per name — one country, plus an optional single
   state (AU) or region. No multi-select, no "+"-joined tokens. Forcing one
   selection keeps names comparable across clients (per CDO). */
function GeoSingleField({ value, onChange }) {
  function parse(v) {
    if (!v) return { country: '', sub: '' };
    if (v === 'INTL' || v === 'AU' || v === 'NZ') return { country: v, sub: '' };
    const i = v.indexOf('-');
    return i === -1 ? { country: v, sub: '' } : { country: v.slice(0, i), sub: v.slice(i + 1) };
  }
  const init = parse(value);
  const [country, setCountry] = useState(init.country);
  const [auState, setAuState] = useState(init.country === 'AU' ? init.sub : '');
  const [region, setRegion] = useState(init.country !== 'AU' ? init.sub : '');

  const isAU = country === 'AU';
  const isINTL = country === 'INTL';
  const hasRegion = country && !isAU && !isINTL;

  function emit(c, st, rg) {
    let token = '';
    if (c === 'INTL') token = 'INTL';
    else if (c === 'AU') token = st ? 'AU-' + st : 'AU';
    else if (c) { const r = (rg || '').trim().toUpperCase().replace(/[\s_+]+/g, '-'); token = r ? c + '-' + r : c; }
    onChange(token);
  }
  function pickCountry(c) { setCountry(c); setAuState(''); setRegion(''); emit(c, '', ''); }
  function pickState(s) { const ns = auState === s ? '' : s; setAuState(ns); emit('AU', ns, ''); }
  function changeRegion(r) { setRegion(r); emit(country, '', r); }

  return (
    <div className="field span2">
      <span className="field-label">Geo
        <span className="locked">— one ISO country, optional single region · one selection only</span>
      </span>
      <div className="geo">
        <div className="geo-sub">
          <span className="tiny">Country</span>
          <select className={'sel' + (country ? '' : ' placeholder')} value={country}
                  onChange={(e) => pickCountry(e.target.value)}>
            <option value="">Select a country…</option>
            <option value="INTL">INTL — International</option>
            <option value="AU">AU — Australia</option>
            <option value="NZ">NZ — New Zealand</option>
            <option disabled>──────────</option>
            {COUNTRIES.map((c) => (<option key={c.c} value={c.c}>{c.c} — {c.n}</option>))}
          </select>
        </div>

        {isAU && (
          <div className="geo-sub">
            <span className="tiny">State — optional, pick one</span>
            <div className="state-grid">
              {S.AU_STATES.map((s) => (
                <button key={s} className="state-chip" aria-pressed={auState === s}
                        onClick={() => pickState(s)}>{s}</button>
              ))}
            </div>
          </div>
        )}

        {hasRegion && (
          <div className="geo-sub">
            <span className="tiny">Region — optional</span>
            <input className="inp" value={region} placeholder={'e.g. ' + country + '-NORTH'}
                   onChange={(e) => changeRegion(e.target.value)} />
          </div>
        )}

        {value
          ? <div className="chips"><span className="chip">{value}</span></div>
          : <span className="field-hint">Pick a country — exactly one geo per name keeps names comparable across clients.</span>}
      </div>
    </div>
  );
}

/* Bulk mode: a geo list is allowed, but each entry is a single token and becomes
   its own campaign — never "+"-joined into one name. Selecting several AU states
   adds each as a separate campaign. */
function GeoBulkField({ geoList, setGeoList }) {
  const [country, setCountry] = useState('');
  const [states, setStates] = useState([]);
  const [region, setRegion] = useState('');

  const isAU = country === 'AU';
  const isINTL = country === 'INTL';
  const hasRegionInput = country && !isAU && !isINTL;

  function reset() { setCountry(''); setStates([]); setRegion(''); }
  function toggleState(s) { setStates((cur) => cur.includes(s) ? cur.filter((x) => x !== s) : [...cur, s]); }

  function add() {
    if (!country) return;
    let tokens = [];
    if (isINTL) tokens = ['INTL'];
    else if (isAU) tokens = states.length ? states.map((s) => 'AU-' + s) : ['AU'];
    else { const r = region.trim().toUpperCase().replace(/[\s_+]+/g, '-'); tokens = [r ? country + '-' + r : country]; }
    const next = [...geoList];
    tokens.forEach((t) => { if (!next.includes(t)) next.push(t); }); // each geo = its own campaign
    setGeoList(next);
    reset();
  }
  const canAdd = !!country;

  return (
    <div className="field span2">
      <span className="field-label">Geo
        <span className="locked">— each geo = one campaign (one geo per name, never joined)</span>
        {geoList.length > 1 && <span className="count-badge">{geoList.length} campaigns</span>}
      </span>
      <div className="geo">
        <div className="geo-controls">
          <div className="geo-sub">
            <span className="tiny">Country</span>
            <select className={'sel' + (country ? '' : ' placeholder')} value={country}
                    onChange={(e) => { setCountry(e.target.value); setStates([]); setRegion(''); }}>
              <option value="">Select a country…</option>
              <option value="INTL">INTL — International</option>
              <option value="AU">AU — Australia</option>
              <option value="NZ">NZ — New Zealand</option>
              <option disabled>──────────</option>
              {COUNTRIES.map((c) => (<option key={c.c} value={c.c}>{c.c} — {c.n}</option>))}
            </select>
          </div>
          <button className="geo-add" onClick={add} disabled={!canAdd}>Add geo</button>
        </div>

        {isAU && (
          <div className="geo-sub">
            <span className="tiny">States — pick any; each becomes its own campaign</span>
            <div className="state-grid">
              {S.AU_STATES.map((s) => (
                <button key={s} className="state-chip" aria-pressed={states.includes(s)}
                        onClick={() => toggleState(s)}>{s}</button>
              ))}
            </div>
          </div>
        )}

        {hasRegionInput && (
          <div className="geo-sub">
            <span className="tiny">Region — optional</span>
            <input className="inp" value={region} placeholder={'e.g. ' + country + '-NORTH'}
                   onChange={(e) => setRegion(e.target.value)}
                   onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); add(); } }} />
          </div>
        )}

        {geoList.length > 0 && (
          <div className="chips">
            {geoList.map((t, i) => (
              <span key={t} className="chip">{t}
                <button className="chip-x" title="Remove"
                        onClick={() => setGeoList(geoList.filter((_, j) => j !== i))}>×</button>
              </span>
            ))}
          </div>
        )}
        {geoList.length === 0 && (
          <span className="field-hint">No geo added yet — add one per geo you want a separate campaign for.</span>
        )}
      </div>
    </div>
  );
}

/* ===================================================================== */
/* Plain fields (free text + locked select)                              */
/* ===================================================================== */
function PlainField({ fieldKey, platform, value, onChange }) {
  const label = S.FIELD_LABEL[fieldKey];
  const opts = S.optionsFor(fieldKey, platform);
  const free = S.isFreeText(fieldKey);
  return (
    <div className="field">
      <span className="field-label">{label}
        {!free && <span className="locked">— locked</span>}
      </span>
      {opts ? (
        <select className={'sel' + (value ? '' : ' placeholder')} value={value || ''}
                onChange={(e) => onChange(e.target.value)}>
          <option value="">Select…</option>
          {opts.map((o) => <option key={o} value={o}>{o}</option>)}
        </select>
      ) : (
        <input className="inp" value={value || ''} placeholder={S.FREE_HINT[fieldKey] || ''}
               onChange={(e) => onChange(e.target.value)} />
      )}
      {free && S.FREE_HINT[fieldKey] && <span className="field-hint">{S.FREE_HINT[fieldKey]}</span>}
    </div>
  );
}

/* ===================================================================== */
/* Bulk field controls                                                   */
/* ===================================================================== */
function MultiSelectField({ fieldKey, platform, selected, onToggle }) {
  const label = S.FIELD_LABEL[fieldKey];
  const opts = S.optionsFor(fieldKey, platform) || [];
  return (
    <div className="field span2">
      <span className="field-label">{label}
        <span className="locked">— pick any; each adds a variant</span>
        {selected.length > 1 && <span className="count-badge">{selected.length} selected</span>}
      </span>
      <div className="state-grid">
        {opts.map((o) => (
          <button key={o} className="state-chip" aria-pressed={selected.includes(o)}
                  onClick={() => onToggle(o)}>{o}</button>
        ))}
      </div>
    </div>
  );
}

function BulkFreeField({ fieldKey, value, onChange }) {
  const label = S.FIELD_LABEL[fieldKey];
  const n = (value || '').split('\n').map((s) => s.trim()).filter(Boolean).length;
  return (
    <div className="field span2">
      <span className="field-label">{label}
        <span className="locked">— one value per line</span>
        {n > 1 && <span className="count-badge">{n} values</span>}
      </span>
      <textarea className="bulk-area" value={value || ''}
                placeholder={(S.FREE_HINT[fieldKey] || '') + '\nOne per line for multiple campaigns'}
                onChange={(e) => onChange(e.target.value)} />
    </div>
  );
}

/* ===================================================================== */
/* Fields step                                                           */
/* ===================================================================== */
function FieldsStep({ platform, level, mode, values, setValue, geo, setGeo,
                      bulkValues, setBulkValue, toggleBulk, bulkGeo, setBulkGeo }) {
  const disabled = !platform || !level;
  const bulk = mode === 'bulk';
  const keys = (platform && level) ? S.fieldKeysForLevel(platform, level) : [];
  return (
    <div className={'step' + (disabled ? ' disabled' : '')}>
      <div className="step-head">
        <span className="step-num">03</span>
        <span className="step-title">Fields</span>
        {!disabled && <span className="step-hint">{bulk ? 'Multiple values multiply into a batch' : 'Locked fields can’t be mistyped'}</span>}
      </div>
      {disabled ? (
        <p className="preview-empty">Choose a platform and level to reveal the fields.</p>
      ) : (
        <div className="fields">
          {keys.map((k) => {
            if (k === 'geo') {
              return bulk
                ? <GeoBulkField key="geo" geoList={bulkGeo} setGeoList={setBulkGeo} />
                : <GeoSingleField key="geo" value={geo} onChange={setGeo} />;
            }
            if (bulk) {
              return S.isFreeText(k)
                ? <BulkFreeField key={k} fieldKey={k} value={bulkValues[k]}
                                 onChange={(v) => setBulkValue(k, v)} />
                : <MultiSelectField key={k} fieldKey={k} platform={platform}
                                    selected={bulkValues[k] || []} onToggle={(o) => toggleBulk(k, o)} />;
            }
            return <PlainField key={k} fieldKey={k} platform={platform}
                               value={values[k]} onChange={(v) => setValue(k, v)} />;
          })}
        </div>
      )}
    </div>
  );
}

/* ===================================================================== */
/* Live name preview                                                     */
/* ===================================================================== */
function valueOf(k, values, geo) {
  if (k === 'geo') return geo || '';               // single token, e.g. AU-WA (no "+")
  const raw = cleanVal(values[k]);
  if (S.isFreeText(k)) return S.formatFree(raw);   // hyphenate free-text spaces
  return raw ? S.abbr(raw) : '';                   // 3-char code for locked fields
}

// Human-readable form of a field, for the decoded expansion under the coded name.
function readableOf(k, values, geo) {
  if (k === 'geo') return geo ? window.MW_LOOKUPS.decodeGeo(geo) : '';
  return cleanVal(values[k]); // free-text as typed; locked stored as its full name
}

function buildName(platform, level, values, geo) {
  const segs = S.SCHEMA[platform].levels[level];
  const missing = [];
  const parts = segs.map((seg) => seg.map((k) => {
    const v = valueOf(k, values, geo);
    if (!v) missing.push(S.FIELD_LABEL[k]);
    return { k, v, label: S.FIELD_LABEL[k], readable: readableOf(k, values, geo) };
  }));
  const clean = segs.map((seg) => seg.map((k) => valueOf(k, values, geo)).join(S.DELIM)).join(S.DELIM);
  const struct = segs.map((seg) => seg.map((k) => '[' + S.FIELD_LABEL[k] + ']').join(S.DELIM)).join(S.DELIM);
  return { parts, missing, clean, struct };
}

/* ---- bulk batch ---- */
const BATCH_HARD_CAP = 2000; // refuse to enumerate beyond this
const BATCH_RENDER_CAP = 300; // rows actually drawn

function batchDims(platform, level, bulkValues, bulkGeo) {
  const keys = S.fieldKeysForLevel(platform, level);
  return keys.map((k) => {
    if (k === 'geo') return { k, vals: bulkGeo.length ? bulkGeo.slice() : [''] };
    if (S.isFreeText(k)) {
      const lines = (bulkValues[k] || '').split('\n').map((s) => cleanVal(s)).filter(Boolean);
      return { k, vals: lines.length ? lines : [''] };
    }
    const sel = bulkValues[k] || [];
    return { k, vals: sel.length ? sel.slice() : [''] };
  });
}

function buildBatch(platform, level, bulkValues, bulkGeo) {
  const dims = batchDims(platform, level, bulkValues, bulkGeo);
  const total = dims.reduce((a, d) => a * d.vals.length, 1);
  if (total > BATCH_HARD_CAP) return { keys: dims.map((d) => d.k), names: [], total, complete: 0, capped: true };

  let combos = [{}];
  dims.forEach((d) => {
    const next = [];
    combos.forEach((c) => d.vals.forEach((v) => next.push(Object.assign({}, c, { [d.k]: v }))));
    combos = next;
  });
  const segs = S.SCHEMA[platform].levels[level];
  const names = combos.map((combo) => {
    let missing = 0;
    const clean = segs.map((seg) =>
      seg.map((k) => {
        const raw = combo[k] || '';
        if (!raw) missing++;
        if (k === 'geo') return raw;
        return S.isFreeText(k) ? S.formatFree(raw) : (raw ? S.abbr(raw) : '');
      }).join(S.DELIM)
    ).join(S.DELIM);
    return { name: clean, complete: missing === 0, combo };
  });
  return { keys: dims.map((d) => d.k), names, total, complete: names.filter((n) => n.complete).length, capped: false };
}

function downloadCSV(filename, header, rows) {
  const esc = (s) => /[",\n]/.test(s) ? '"' + String(s).replace(/"/g, '""') + '"' : String(s);
  const csv = [header.map(esc).join(',')].concat(rows.map((r) => r.map(esc).join(','))).join('\r\n');
  const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url; a.download = filename; document.body.appendChild(a); a.click(); a.remove();
  setTimeout(() => URL.revokeObjectURL(url), 1500);
}

function PreviewPane({ platform, level, values, geo, dark, onCopy, recent, onCopyRecent, onClearRecent }) {
  const ready = platform && level;
  const data = ready ? buildName(platform, level, values, geo) : null;
  const complete = data && data.missing.length === 0;

  return (
    <aside className="preview">
      <div className={'name-card' + (dark ? ' dark' : '')}>
        <div className="name-eyebrow">Generated name</div>
        <div className="name-context">
          {ready ? `${platform} · ${level}`
                 : <span className="ph">Select a platform &amp; level</span>}
        </div>

        {ready ? (
          <React.Fragment>
            <div className="name-body">
              {data.parts.map((seg, si) => (
                <React.Fragment key={si}>
                  {si > 0 && <span className="sep">_</span>}
                  {seg.map((f, fi) => (
                    <React.Fragment key={f.k}>
                      {fi > 0 && <span className="sep">_</span>}
                      <span className={'tok ' + (f.v ? 'tok-val' : 'tok-empty')}>
                        {f.v || '[' + f.label + ']'}
                      </span>
                    </React.Fragment>
                  ))}
                </React.Fragment>
              ))}
            </div>
            <div className="name-readable" title="What the codes mean (via lookup tables)">
              {data.parts.reduce((a, s) => a.concat(s), []).map((f, i) => (
                <React.Fragment key={i}>
                  {i > 0 && <span className="rsep">·</span>}
                  <span className={'rtok' + (f.readable ? '' : ' empty')}>
                    {f.readable || '[' + f.label + ']'}
                  </span>
                </React.Fragment>
              ))}
            </div>
            <div className="name-actions">
              <button className="btn-copy" disabled={!complete}
                      onClick={() => onCopy(data.clean, `${platform} · ${level}`)}>
                Copy name
              </button>
              <span className={'status ' + (complete ? 'ready' : 'miss')}>
                {complete ? 'Ready to copy'
                          : `${data.missing.length} field${data.missing.length > 1 ? 's' : ''} to complete`}
              </span>
            </div>
          </React.Fragment>
        ) : (
          <p className="preview-empty">Your campaign name will assemble here as you go.</p>
        )}
      </div>

      {ready && (
        <div className="struct">
          <span className="sb">Structure</span>&nbsp;&nbsp;{data.struct}
        </div>
      )}

      <div className="recent">
        <div className="recent-head">
          <h3>Recent names</h3>
          {recent.length > 0 && <button className="recent-clear" onClick={onClearRecent}>Clear</button>}
        </div>
        {recent.length === 0 ? (
          <p className="recent-empty">Names you copy are saved here for quick reuse.</p>
        ) : (
          <div className="recent-list">
            {recent.map((r, i) => (
              <div className="recent-item" key={i}>
                <div className="recent-meta">
                  <div className="recent-ctx">{r.ctx}</div>
                  <div className="recent-name" title={r.name}>{r.name}</div>
                </div>
                <button className="recent-copy" onClick={() => onCopyRecent(r.name)}>Copy</button>
              </div>
            ))}
          </div>
        )}
      </div>
    </aside>
  );
}

/* ===================================================================== */
/* Mode switch                                                           */
/* ===================================================================== */
function ModeSwitch({ mode, onPick }) {
  return (
    <div className="modebar">
      <span className="ms-label">Mode</span>
      <div className="ms-group">
        <button className="ms-btn" aria-pressed={mode === 'single'} onClick={() => onPick('single')}>Single name</button>
        <button className="ms-btn" aria-pressed={mode === 'bulk'} onClick={() => onPick('bulk')}>Bulk batch</button>
      </div>
      <span className="ms-note">
        {mode === 'bulk'
          ? 'Give any field several values — the tool builds every combination at once.'
          : 'Build one name at a time, ready to copy.'}
      </span>
    </div>
  );
}

/* ===================================================================== */
/* Bulk batch pane                                                       */
/* ===================================================================== */
function BatchPane({ platform, level, bulkValues, bulkGeo, onCopyAll, onCopyOne, onDownload }) {
  const ready = platform && level;
  if (!ready) {
    return (
      <aside className="preview">
        <div className="batch-card">
          <div className="name-eyebrow">Batch</div>
          <p className="preview-empty">Select a platform &amp; level, then give fields multiple values to build a batch.</p>
        </div>
      </aside>
    );
  }

  const data = buildBatch(platform, level, bulkValues, bulkGeo);
  const shown = data.names.slice(0, BATCH_RENDER_CAP);

  return (
    <aside className="preview">
      <div className="batch-card">
        <div className="name-eyebrow">{platform} · {level} · batch</div>
        {data.capped ? (
          <React.Fragment>
            <div className="batch-count">{data.total.toLocaleString()}</div>
            <p className="batch-sub">That’s a lot of combinations — narrow a field or two (under {BATCH_HARD_CAP.toLocaleString()}) to generate the list.</p>
          </React.Fragment>
        ) : (
          <React.Fragment>
            <div className="batch-top">
              <span className="batch-count">{data.total}</span>
            </div>
            <p className="batch-sub">
              {data.total === 1 ? '1 name' : data.total + ' names'}
              {data.complete < data.total && ` · ${data.complete} ready, ${data.total - data.complete} incomplete`}
              {data.complete === data.total && ' · all ready'}
            </p>
            <div className="batch-actions">
              <button className="btn-copy" disabled={!data.complete}
                      onClick={() => onCopyAll(data.names)}>Copy all ready</button>
              <button className="btn-ghost" disabled={!data.complete}
                      onClick={() => onDownload(data)}>Download CSV</button>
            </div>
            <div className="batch-list">
              {shown.map((r, i) => (
                <div className="batch-row" key={i}>
                  <span className="batch-idx">{String(i + 1).padStart(2, '0')}</span>
                  <span className={'batch-name' + (r.complete ? '' : ' incomplete')}>{r.name}</span>
                  {r.complete && <button className="recent-copy" onClick={() => onCopyOne(r.name)}>Copy</button>}
                </div>
              ))}
              {data.names.length > BATCH_RENDER_CAP && (
                <p className="batch-more">+{data.names.length - BATCH_RENDER_CAP} more — included in Copy all &amp; CSV.</p>
              )}
            </div>
          </React.Fragment>
        )}
      </div>
    </aside>
  );
}
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "density": "comfortable",
  "preview": "light",
  "accent": "#004D45"
}/*EDITMODE-END*/;

const ACCENTS = {
  '#004D45': '#003A34', // dark green (brand)
  '#1A1A18': '#000000', // near-black
  '#004A6E': '#003650'  // dark blue (secondary palette)
};

function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const [view, setView] = useState('taxonomy');
  const [clients, setClients] = useState(function () { return window.MW_UTM.SEED_CLIENTS; });
  const [mode, setMode] = useState('single');
  const [platform, setPlatform] = useState(null);
  const [level, setLevel] = useState(null);
  const [values, setValues] = useState({});
  const [geo, setGeo] = useState('');           // single geo token for single mode
  const [bulkValues, setBulkValues] = useState({});
  const [bulkGeo, setBulkGeo] = useState([]);
  const [recent, setRecent] = useState(() => {
    try { return JSON.parse(localStorage.getItem('mw_tax_recent') || '[]'); } catch (e) { return []; }
  });
  const [toast, setToast] = useState('');
  const toastTimer = useRef(null);

  /* density + accent applied to body / root */
  useEffect(() => {
    document.body.classList.toggle('compact', t.density === 'compact');
  }, [t.density]);
  useEffect(() => {
    const root = document.documentElement;
    root.style.setProperty('--accent', t.accent);
    root.style.setProperty('--accent-hover', ACCENTS[t.accent] || '#003A34');
  }, [t.accent]);

  function pickPlatform(p) {
    setPlatform(p); setLevel(null);
    const prune = (obj, isArr) => {
      const next = { ...obj };
      ['campaignType', 'optGoal', 'adType', 'creativeType'].forEach((k) => {
        const opts = S.optionsFor(k, p);
        if (!opts) return;
        if (isArr) {
          if (next[k]) next[k] = next[k].filter((v) => opts.indexOf(v) !== -1);
        } else if (opts.indexOf(next[k]) === -1) { delete next[k]; }
      });
      return next;
    };
    setValues((cur) => prune(cur, false));
    setBulkValues((cur) => prune(cur, true));
  }

  function setValue(k, v) { setValues((cur) => ({ ...cur, [k]: v })); }
  function setBulkValue(k, v) { setBulkValues((cur) => ({ ...cur, [k]: v })); }
  function toggleBulk(k, opt) {
    setBulkValues((cur) => {
      const arr = cur[k] || [];
      const next = arr.includes(opt) ? arr.filter((x) => x !== opt) : [...arr, opt];
      return { ...cur, [k]: next };
    });
  }

  function showToast(msg) {
    setToast(msg);
    clearTimeout(toastTimer.current);
    toastTimer.current = setTimeout(() => setToast(''), 1800);
  }

  function copyText(text) {
    if (navigator.clipboard && navigator.clipboard.writeText) {
      navigator.clipboard.writeText(text).catch(() => fallbackCopy(text));
    } else { fallbackCopy(text); }
  }
  function fallbackCopy(text) {
    const ta = document.createElement('textarea');
    ta.value = text; document.body.appendChild(ta); ta.select();
    try { document.execCommand('copy'); } catch (e) {}
    ta.remove();
  }

  function handleCopy(name, ctx) {
    copyText(name);
    showToast('Copied to clipboard');
    setRecent((cur) => {
      const next = [{ name, ctx }, ...cur.filter((r) => r.name !== name)].slice(0, 6);
      try { localStorage.setItem('mw_tax_recent', JSON.stringify(next)); } catch (e) {}
      return next;
    });
  }
  function handleCopyRecent(name) { copyText(name); showToast('Copied to clipboard'); }
  function clearRecent() {
    setRecent([]);
    try { localStorage.removeItem('mw_tax_recent'); } catch (e) {}
  }

  /* ---- bulk handlers ---- */
  function copyAll(names) {
    const ready = names.filter((n) => n.complete).map((n) => n.name);
    if (!ready.length) return;
    copyText(ready.join('\n'));
    showToast(`Copied ${ready.length} name${ready.length > 1 ? 's' : ''}`);
  }
  function copyOne(name) { copyText(name); showToast('Copied to clipboard'); }
  function downloadBatch(data) {
    const ready = data.names.filter((n) => n.complete);
    if (!ready.length) return;
    const header = ['Level'].concat(data.keys.map((k) => S.FIELD_LABEL[k])).concat(['Name']);
    const rows = ready.map((r) =>
      [level].concat(data.keys.map((k) => r.combo[k] || '')).concat([r.name])
    );
    const stamp = new Date().toISOString().slice(0, 10);
    downloadCSV(`mw-${platform}-${level}-${stamp}.csv`.replace(/\s+/g, '-').toLowerCase(), header, rows);
    showToast(`Downloaded ${ready.length} names`);
  }

  return (
    <React.Fragment>
      <header className="topbar">
        <div className="brand">
          <img className="wordmark" src={(window.__resources && window.__resources.wordmark) || 'assets/logo-wordmark.svg'} alt="Match & Wood" />
          <span className="divider"></span>
          <nav className="tabs">
            <button className="tab" aria-pressed={view === 'taxonomy'} onClick={() => setView('taxonomy')}>Taxonomy</button>
            <button className="tab" aria-pressed={view === 'utm'} onClick={() => setView('utm')}>UTM builder</button>
          </nav>
        </div>
        <div className="topbar-right">
          <span className="meta">{view === 'admin' ? 'Client config' : view === 'utm' ? 'Tracked URL builder' : 'M\u0026W 2026 V2 naming convention'}</span>
          <button className="admin-link" aria-pressed={view === 'admin'} onClick={() => setView('admin')} title="Configure UTM templates">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
              <circle cx="12" cy="12" r="3"></circle>
              <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"></path>
            </svg>
            Admin
          </button>
        </div>
      </header>

      {view === 'taxonomy' && (
      <div className="wrap">
        <main>
          <div className="intro">
            <h1>Build a compliant campaign name</h1>
            <p>Fixed fields are locked to the 2026 convention so they can’t be mistyped;
               identifiers stay free text. Pick a platform, choose the level, fill the fields —
               the name assembles itself.</p>
          </div>

          <ModeSwitch mode={mode} onPick={setMode} />
          <PlatformStep platform={platform} onPick={pickPlatform} />
          <LevelStep platform={platform} level={level} onPick={setLevel} />
          <FieldsStep platform={platform} level={level} mode={mode}
                      values={values} setValue={setValue} geo={geo} setGeo={setGeo}
                      bulkValues={bulkValues} setBulkValue={setBulkValue} toggleBulk={toggleBulk}
                      bulkGeo={bulkGeo} setBulkGeo={setBulkGeo} />
        </main>

        {mode === 'bulk' ? (
          <BatchPane platform={platform} level={level} bulkValues={bulkValues} bulkGeo={bulkGeo}
                     onCopyAll={copyAll} onCopyOne={copyOne} onDownload={downloadBatch} />
        ) : (
          <PreviewPane platform={platform} level={level} values={values} geo={geo}
                       dark={t.preview === 'dark'} onCopy={handleCopy} recent={recent}
                       onCopyRecent={handleCopyRecent} onClearRecent={clearRecent} />
        )}

        <div className="legend">
          <b>How it works.</b> All components join with “_”; locked fields render as ≤3-letter codes
          and free-text spaces become “-”. Geo is a single ISO country with an optional region
          (e.g. AU-WA) or INTL — exactly one geo per name, never “+”-joined, so names stay comparable
          across clients. The readable line under the name decodes the codes via the shared lookup
          tables (Admin → Naming lookup tables to export them for dashboards).
          {mode === 'bulk' && ' In bulk mode, give any field several values and every combination is generated — each geo produces its own campaign. Export the batch as CSV for Google Ads Editor.'}
        </div>
      </div>
      )}

      {view === 'utm' && (
        <UtmView clients={clients} copyText={copyText} onToast={showToast} />
      )}
      {view === 'admin' && (
        <AdminView clients={clients} setClients={setClients} onToast={showToast} />
      )}

      <div className={'toast' + (toast ? ' show' : '')}>{toast}</div>

      <TweaksPanel>
        <TweakSection label="Layout" />
        <TweakRadio label="Density" value={t.density} options={['comfortable', 'compact']}
                    onChange={(v) => setTweak('density', v)} />
        <TweakSection label="Generated name" />
        <TweakRadio label="Preview card" value={t.preview} options={['light', 'dark']}
                    onChange={(v) => setTweak('preview', v)} />
        <TweakSection label="Accent" />
        <TweakColor label="Accent colour" value={t.accent}
                    options={Object.keys(ACCENTS)}
                    onChange={(v) => setTweak('accent', v)} />
      </TweaksPanel>
    </React.Fragment>
  );
}

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