// Property Match — review the system's recommendations and confirm.
// User can pick: one / multiple / generic / no suitable property.
//
// Nothing is ever pre-selected. The system suggests; the user decides.

function PropertyMatch({
  brands, selectedBrandId, onSelectBrand, onConfirmMatch, onNavigate,
  onUpdateBrand, onMoveToResearch, onToast, currentUser, onReloadProperties,
}) {
  const { PROPERTIES } = window.IPSEM_DATA;

  // The SAME active population as Brand Research — the two pages show the same
  // three tabs and are two halves of one decision, so they have to count the
  // same brands. This used to hand-roll its own test
  // (research complete && priority not no-action/kiv), which let 3 brands with
  // closed outreach in and kept 17 unresearched ones out: 274 here against 288
  // on Research, for no reason a reader could see.
  const eligible = brands.filter(pbActive);

  // ─── Filters ────────────────────────────────────────────────────────────
  // Shared with Research, Contacts and Draft (useBrandFilters in ui.jsx) — one
  // tier / category / assignee / country setting for the whole flow.
  const f = useBrandFilters(eligible);
  // Same three-way cut as every other stage. Shares the storage key with Brand
  // Research on purpose: the two pages are two halves of one decision, so
  // switching to "Decided" on one and back should not surprise you on the other.
  const [reviewFilter, setReviewFilter] = usePersistentState('filters.review', 'needs'); // needs | decided | all
  const filtersActive = f.active || reviewFilter !== 'all';

  const needsCount   = React.useMemo(() => eligible.filter(needsPropertyDecision).length, [eligible]);
  const decidedCount = React.useMemo(() => eligible.filter(isDecided).length, [eligible]);
  const earlierCount = React.useMemo(() => eligible.filter(awaitingEarlierStage).length, [eligible]);

  const filteredEligible = React.useMemo(
    () => eligible.filter((b) => {
      if (!f.matches(b)) return false;
      if (reviewFilter === 'needs' && !needsPropertyDecision(b)) return false;
      if (reviewFilter === 'decided' && !isDecided(b)) return false;
      return true;
    }),
    [eligible, f.matches, reviewFilter]
  );

  // Awaiting-decision brands (no confirmed property yet) always sit on top so
  // they're seen first; within each group, newest review date first.
  const awaiting = (b) => (b.confirmedProperties || []).length === 0;
  const sorted = React.useMemo(() => {
    return [...filteredEligible].sort((a, b) => {
      const aw = awaiting(a), bw = awaiting(b);
      if (aw !== bw) return aw ? -1 : 1;
      // createdAt then id break the date ties, so the list keeps the same order
      // on every refresh instead of reshuffling under whoever is working it.
      return (b.reviewDate || '').localeCompare(a.reviewDate || '')
        || (b.createdAt || '').localeCompare(a.createdAt || '')
        || String(a.id || '').localeCompare(String(b.id || ''));
    });
  }, [filteredEligible]);

  const selected = brands.find((b) => b.id === selectedBrandId) || sorted[0];

  React.useEffect(() => {
    if (!selectedBrandId && sorted[0]) onSelectBrand(sorted[0].id);
  }, []);

  // ─── Bulk actions ───────────────────────────────────────────────────────
  // Property choice is per brand, but re-assigning or parking a batch is not —
  // so the same tick-and-act bar News Intake has now works here too.
  const bulk = useBulkSelect(sorted);
  const applyBulk = (fn, label) => {
    const ids = bulk.ids;
    ids.forEach(fn);
    bulk.clear();
    onToast && onToast(`${ids.length} brand${ids.length === 1 ? '' : 's'} ${label}`);
  };
  const bulkActions = [
    {
      label: 'KIV', icon: 'eye',
      title: 'Keep in view — held out of the active flow until reactivated',
      onClick: () => applyBulk((id) => onUpdateBrand(id, {
        status: 'maybe', priority: 'kiv', researchStatus: 'kiv', userOverride: true,
      }), 'kept in view'),
    },
    {
      label: 'Reject', icon: 'x', className: 'btn btn-sm btn-ghost', danger: true,
      title: 'Stop these brands',
      onClick: () => applyBulk((id) => onUpdateBrand(id, {
        status: 'not-relevant', priority: 'no-action', researchStatus: 'rejected', userOverride: true,
      }), 'rejected'),
    },
  ];

  // Track the user's selection — what they're considering before confirming
  const [draftSelection, setDraftSelection] = React.useState(() => selected?.confirmedProperties || []);
  const [draftDisciplines, setDraftDisciplines] = React.useState(() => selected?.confirmedDisciplines || {});
  const [propSearch, setPropSearch] = React.useState('');
  const [showAllProps, setShowAllProps] = React.useState(false);
  const [showOneOff, setShowOneOff] = React.useState(false);
  React.useEffect(() => {
    setDraftSelection(selected?.confirmedProperties || []);
    setDraftDisciplines(selected?.confirmedDisciplines || {});
    setPropSearch('');
    setShowAllProps(false);
  }, [selected?.id]);

  // Nothing to work on. Name the one thing that fixes it rather than stating
  // the obvious — the previous copy left the user guessing what to do next.
  if (!selected) {
    return (
      <div className="page page-cap">
        <SectionHeader
          eyebrow="Step 03 · System recommended · You decide"
          title="Property match"
          sub="Brands arrive here once their research profile is complete."
        />
        <Card lg>
          <div className="empty">
            <div style={{ fontWeight: 600, color: 'var(--ink)' }}>
              {filtersActive ? 'No brands match these filters.' : 'Nothing is ready to match yet.'}
            </div>
            <div className="text-sm" style={{ marginTop: 6, maxWidth: 460, marginInline: 'auto', lineHeight: 1.6 }}>
              {filtersActive
                ? 'The shared tier / category / assignee / country filters are hiding everything eligible. Clear them to carry on.'
                : 'A brand reaches this stage when its research is complete. Finish a profile in Brand Research, or run a scan to pull in new brands.'}
            </div>
            <div className="row gap-2" style={{ marginTop: 14, justifyContent: 'center' }}>
              {filtersActive ? (
                <button className="btn btn-sm btn-primary" onClick={f.clear}>Clear filters</button>
              ) : (
                <>
                  <button className="btn btn-sm btn-primary" onClick={() => onNavigate('research')}>
                    Open Brand Research
                    <window.I.arrowRight size={12} />
                  </button>
                  <button className="btn btn-sm" onClick={() => onNavigate('intake')}>Open News Intake</button>
                </>
              )}
            </div>
          </div>
        </Card>
      </div>
    );
  }

  // Suggested = the research engine's potential list + the AI's recommendations.
  const suggestedIds = new Set([
    ...((selected.potentialProperties) || []),
    ...((selected.aiRecommendation && selected.aiRecommendation.properties) || []),
  ]);

  // Rank EVERY ACTIVE property so the user can pick any of them — suggested
  // first, then by fit score. Archived properties (e.g. ones we've switched off
  // in the Properties library) never appear. A search box narrows the list.
  const q = propSearch.trim().toLowerCase();
  const ranked = PROPERTIES
    .filter((p) => !p.archived)
    .map((p) => ({ prop: p, fit: getFit(selected, p), suggested: suggestedIds.has(p.id) }))
    .filter(({ prop }) => {
      if (!q) return true;
      return (prop.name || '').toLowerCase().includes(q)
        || (prop.sport || '').toLowerCase().includes(q)
        || (prop.disciplines || []).join(' ').toLowerCase().includes(q)
        || (prop.keyMarkets || []).join(' ').toLowerCase().includes(q);
    })
    .sort((a, b) => (Number(b.suggested) - Number(a.suggested)) || (b.fit.total - a.fit.total));

  const suggestedList = ranked.filter((r) => r.suggested);
  const otherList = ranked.filter((r) => !r.suggested);

  const isGeneric = draftSelection.includes('generic');
  const isNone = draftSelection.includes('none');

  const togglePick = (pid) => {
    if (isGeneric || isNone) return; // mutually exclusive
    setDraftSelection((cur) => cur.includes(pid) ? cur.filter((x) => x !== pid) : [...cur, pid]);
    // Dropping a property drops its discipline tags with it.
    setDraftDisciplines((cur) => {
      if (!cur[pid]) return cur;
      const next = { ...cur };
      delete next[pid];
      return next;
    });
  };

  // Tag an individual discipline (FEI → Dressage) without splitting the property.
  const toggleDiscipline = (pid, name) => {
    setDraftSelection((cur) => cur.includes(pid) ? cur : [...cur.filter((x) => x !== 'generic' && x !== 'none'), pid]);
    setDraftDisciplines((cur) => {
      const list = cur[pid] || [];
      const next = list.includes(name) ? list.filter((x) => x !== name) : [...list, name];
      const out = { ...cur };
      if (next.length) out[pid] = next; else delete out[pid];
      return out;
    });
  };

  const pickMode = (mode) => {
    if (mode === 'generic') { setDraftSelection(['generic']); setDraftDisciplines({}); }
    else if (mode === 'none') { setDraftSelection(['none']); setDraftDisciplines({}); }
    else if (mode === 'specific') setDraftSelection([]);
  };

  const savedSelection = selected.confirmedProperties || [];
  const hasSavedSelection = savedSelection.length > 0;
  // An empty selection is now saveable — that is how you REMOVE an approved
  // property. It is only blocked when there was nothing approved to begin with.
  const confirmDisabled = draftSelection.length === 0 && !hasSavedSelection;
  const isRemoval = draftSelection.length === 0 && hasSavedSelection;
  const changed = JSON.stringify(draftSelection) !== JSON.stringify(savedSelection) ||
    JSON.stringify(draftDisciplines) !== JSON.stringify(selected.confirmedDisciplines || {});

  // Which brand to move on to once this one is decided.
  const nextAwaiting = sorted.find((b) => b.id !== selected.id && awaiting(b));

  const handleOneOffCreated = async (prop) => {
    await window.IPSEM_API.createProperty(prop);
    if (onReloadProperties) await onReloadProperties();
    setShowOneOff(false);
    setDraftSelection((cur) => [...cur.filter((x) => x !== 'generic' && x !== 'none'), prop.id]);
    onToast && onToast(`${prop.name} added and selected`);
  };

  return (
    <div className="page page-cap">
      <SectionHeader
        eyebrow="Step 03 · System recommended · You decide"
        title="Property match"
        sub="For each researched brand, the system scores fit against every IPSEM property based on market, audience, values, category and timing. Nothing is pre-selected — you pick which property/properties go forward."
      />

      <BrandBulkBar
        bulk={bulk}
        actions={bulkActions}
        onAssign={(name) => { if (name) applyBulk((id) => onUpdateBrand(id, { assignee: name }), `assigned to ${name}`); }}
      />

      <div className="responsive-split">
        <Card pad={false} style={{ position: 'sticky', top: 80, maxHeight: 'calc(100vh - 120px)', overflowY: 'auto' }}>
          <div style={{ padding: '12px 14px', borderBottom: '1px solid var(--hairline)' }}>
            <div className="row" style={{ justifyContent: 'space-between', alignItems: 'flex-start' }}>
              <div>
                <div className="eyebrow">Ready for match</div>
                <div className="text-xs text-3" style={{ marginTop: 4 }}>
                  {reviewFilter === 'needs'
                    ? `${needsCount} to review · ${decidedCount} decided`
                    : `${filteredEligible.length} of ${eligible.length} brand${eligible.length === 1 ? '' : 's'}`}
                  {earlierCount ? ` · ${earlierCount} still waiting on News Intake` : ''}
                </div>
              </div>
              <div className="row gap-1">
                {sorted.length > 0 ? (
                  <label
                    className="btn btn-icon btn-ghost"
                    title={bulk.allSelected ? 'Unselect all shown' : 'Select all shown'}
                    style={{ cursor: 'pointer' }}
                  >
                    <input type="checkbox" checked={bulk.allSelected} onChange={bulk.toggleAll} />
                  </label>
                ) : null}
                <button
                  className="btn btn-icon btn-ghost"
                  onClick={() => f.setOpen((x) => !x)}
                  title="Filter brands"
                  style={{ color: filtersActive ? 'var(--accent)' : 'var(--ink-3)' }}
                >
                  <window.I.filter size={14} />
                </button>
              </div>
            </div>
            {filtersActive && !f.open ? (
              <div className="text-xs" style={{ marginTop: 6, color: 'var(--accent)' }}>
                Filters are on and will stay on until you clear them.
              </div>
            ) : null}
          </div>

          <StageFilterBar
            value={reviewFilter}
            onChange={setReviewFilter}
            options={[
              { id: 'needs',   label: 'To review', count: needsCount,     title: 'Researched, no property confirmed yet' },
              { id: 'decided', label: 'Decided',   count: decidedCount,   title: 'A property is on record, or a person marked the brand reviewed' },
              { id: 'all',     label: 'All',       count: eligible.length,
                title: earlierCount
                  ? `Every active brand — the same list and the same counts as Brand Research. ${earlierCount} have not been researched yet and are decided in News Intake.`
                  : 'Every active brand — the same list and the same counts as Brand Research' },
            ]}
          />

          {f.open ? <BrandFilterPanel f={f} /> : null}

          {sorted.map((b) => {
            const isSel = selected && selected.id === b.id;
            const confirmed = (b.confirmedProperties || []).length > 0;
            return (
              <div
                key={b.id}
                onClick={() => onSelectBrand(b.id)}
                style={{
                  padding: '12px 14px',
                  borderBottom: '1px solid var(--hairline)',
                  cursor: 'pointer',
                  background: isSel ? 'var(--accent-soft-bg)' : 'transparent',
                  borderLeft: isSel ? '3px solid var(--accent)' : '3px solid transparent',
                }}
              >
                <div className="row gap-2" style={{ alignItems: 'center' }}>
                  <input
                    type="checkbox"
                    checked={bulk.has(b.id)}
                    onClick={(e) => e.stopPropagation()}
                    onChange={() => bulk.toggle(b.id)}
                    title="Select for a bulk action"
                  />
                  <BrandLogo brand={b} size={22} />
                  <span style={{ fontWeight: 600, fontSize: 13.5 }}>{b.brand}</span>
                  {confirmed ? <window.I.check size={13} stroke="var(--positive)" /> : null}
                  <span className="right"><PriorityPill priority={b.priority} /></span>
                </div>
                <div className="text-xs text-3" style={{ marginTop: 4, paddingLeft: 30 }}>
                  {b.confirmedProperties && b.confirmedProperties.length ? (
                    b.confirmedProperties.map((p) => p === 'generic' ? 'Generic' : p === 'none' ? 'No match' : propLabel(p)).join(', ')
                  ) : (
                    <span style={{ color: 'var(--pending)' }}>Awaiting your decision</span>
                  )}
                </div>
                <div className="row gap-1" style={{ marginTop: 6, paddingLeft: 30, flexWrap: 'wrap', alignItems: 'center' }}>
                  <BrandStatusChip brand={b} onNavigate={onNavigate} />
                  {/* Sign-off on the row, same control and wording as every
                      other stage, so ticking a brand off never means hunting. */}
                  {onUpdateBrand ? (
                    <span className="right" onClick={(e) => e.stopPropagation()}>
                      <MarkReviewedControl
                        brand={b}
                        currentUser={currentUser}
                        onUpdateBrand={onUpdateBrand}
                        onToast={onToast}
                      />
                    </span>
                  ) : null}
                </div>
              </div>
            );
          })}
        </Card>

        <div className="col gap-3">
          {/* Brand context strip */}
          <Card>
            <div className="row gap-3" style={{ flexWrap: 'wrap' }}>
              <BrandLogo brand={selected} size={38} radius={9} />
              <div className="grow" style={{ minWidth: 200 }}>
                <div className="row gap-2">
                  <span className="h3">{selected.brand}</span>
                  <span className="text-3 text-sm">· {selected.category}</span>
                </div>
                <div className="text-2 text-sm truncate" style={{ marginTop: 2, maxWidth: 700 }}>
                  {selected.news?.title}
                </div>
              </div>
              <div className="row gap-2" style={{ flexWrap: 'wrap' }}>
                <button className="btn btn-sm btn-ghost" onClick={() => onNavigate('research', selected.id)}>
                  Open research
                  <window.I.arrowRight size={12} />
                </button>
              </div>
            </div>
          </Card>

          {/* Tier / status / relationship / outreach / assignee / actions — the
              same strip on every stage. */}
          <BrandControlBar
            brand={selected}
            onUpdateBrand={onUpdateBrand}
            onMoveToResearch={onMoveToResearch}
            onToast={onToast}
            currentUser={currentUser}
          />

          {/* Notes — same panel, same position as every other stage: directly
              under the brand header. Reads and writes the one shared list. */}
          {onUpdateBrand ? (
            <NotesPanel
              brand={selected}
              currentUser={currentUser}
              onUpdateBrand={onUpdateBrand}
              onToast={onToast}
            />
          ) : null}

          {/* Match candidates */}
          <div>
            <div className="row" style={{ justifyContent: 'space-between', marginBottom: 12, flexWrap: 'wrap', gap: 10 }}>
              <div>
                <div className="eyebrow" style={{ marginBottom: 4 }}>Choose properties</div>
                <div className="text-2 text-sm">
                  Suggested first, or search the full portfolio. Click a card to select or unselect it.
                </div>
              </div>
              <div className="toggle-group">
                <button className={!isGeneric && !isNone ? 'is-active' : ''} onClick={() => pickMode('specific')}>Specific</button>
                <button className={isGeneric ? 'is-active' : ''} onClick={() => pickMode('generic')}>Generic</button>
                <button className={isNone ? 'is-active' : ''} onClick={() => pickMode('none')}>No match</button>
              </div>
            </div>

            {isGeneric ? (
              <Card lg style={{ background: 'var(--accent-soft-bg)' }}>
                <div className="row gap-2" style={{ marginBottom: 6 }}>
                  <window.I.sparkle size={14} stroke="var(--accent)" />
                  <strong>Generic outreach selected</strong>
                </div>
                <div className="text-2 text-sm">
                  The draft will avoid naming a specific property — IPSEM's wider portfolio will be positioned as relevant.
                  Use this when the brand fits most or all of your portfolio.
                </div>
              </Card>
            ) : isNone ? (
              <Card lg className="banner-stopped" style={{ display: 'block' }}>
                <div className="row gap-2" style={{ marginBottom: 6 }}>
                  <window.I.ban size={14} />
                  <strong>No suitable property</strong>
                </div>
                <div className="text-2 text-sm">
                  Flow will stop here. No contact research and no draft will be generated for this brand.
                </div>
              </Card>
            ) : (
              <div className="col gap-3">
                {/* Search across the full portfolio + add a property that isn't in it */}
                <div className="row gap-2" style={{ flexWrap: 'wrap' }}>
                  <div className="field grow" style={{ minWidth: 220 }}>
                    <window.I.search size={14} stroke="var(--ink-3)" />
                    <input
                      placeholder="Search all properties by name, sport, discipline or market…"
                      value={propSearch}
                      onChange={(e) => setPropSearch(e.target.value)}
                    />
                    {propSearch ? (
                      <button className="btn btn-icon btn-ghost" onClick={() => setPropSearch('')} title="Clear">
                        <window.I.x size={13} />
                      </button>
                    ) : null}
                  </div>
                  <button
                    className="btn"
                    onClick={() => setShowOneOff(true)}
                    title="Add a property that isn't in the portfolio — e.g. a one-off we can work on short term"
                  >
                    <window.I.plus size={13} />
                    Add one-off property
                  </button>
                </div>

                {/* Suggested properties (research + AI) — shown first */}
                {suggestedList.length > 0 ? (
                  <div className="col gap-3">
                    <div className="eyebrow row gap-1">
                      <window.I.sparkle size={11} stroke="var(--accent)" />
                      Suggested for {selected.brand} ({suggestedList.length})
                      <span className="text-xs text-3" style={{ textTransform: 'none', letterSpacing: 0, fontWeight: 400, marginLeft: 6 }}>
                        — suggestions only, nothing is selected until you select it
                      </span>
                    </div>
                    {suggestedList.map(({ prop, fit }) => (
                      <MatchCard
                        key={prop.id}
                        brand={selected}
                        prop={prop}
                        fit={fit}
                        picked={draftSelection.includes(prop.id)}
                        pickedDisciplines={draftDisciplines[prop.id] || []}
                        onToggle={() => togglePick(prop.id)}
                        onToggleDiscipline={(name) => toggleDiscipline(prop.id, name)}
                      />
                    ))}
                  </div>
                ) : (
                  <Card>
                    <div className="text-2 text-sm">
                      No properties were auto-suggested for this brand. Browse the full portfolio below and pick the best fit yourself.
                    </div>
                  </Card>
                )}

                {/* Browse the rest of the portfolio */}
                {otherList.length > 0 ? (
                  <div className="col gap-3">
                    <button
                      className="row gap-2"
                      onClick={() => setShowAllProps((x) => !x)}
                      style={{ background: 'transparent', border: 0, cursor: 'pointer', padding: '4px 0', alignItems: 'center' }}
                    >
                      <span className="eyebrow">
                        {q ? `Other matches (${otherList.length})` : `Browse all properties (${otherList.length})`}
                      </span>
                      <window.I.chevronDown
                        size={14}
                        stroke="var(--ink-3)"
                        style={{ transform: (showAllProps || q) ? 'rotate(180deg)' : 'none', transition: 'transform 120ms' }}
                      />
                    </button>
                    {(showAllProps || q) ? (
                      otherList.map(({ prop, fit }) => (
                        <MatchCard
                          key={prop.id}
                          brand={selected}
                          prop={prop}
                          fit={fit}
                          picked={draftSelection.includes(prop.id)}
                          pickedDisciplines={draftDisciplines[prop.id] || []}
                          onToggle={() => togglePick(prop.id)}
                          onToggleDiscipline={(name) => toggleDiscipline(prop.id, name)}
                        />
                      ))
                    ) : null}
                  </div>
                ) : null}

                {ranked.length === 0 ? (
                  <Card><div className="empty">No properties match "{propSearch}".</div></Card>
                ) : null}
              </div>
            )}
          </div>

          {/* Action bar */}
          <Card lg style={{
            position: 'sticky',
            bottom: 22,
            zIndex: 5,
            border: changed ? '1px solid var(--accent)' : '1px solid var(--hairline)',
            boxShadow: changed ? '0 6px 24px -10px rgba(29,78,216,0.35)' : 'var(--shadow-sm)',
          }}>
            <div className="row gap-3" style={{ flexWrap: 'wrap' }}>
              <div className="grow" style={{ minWidth: 220 }}>
                <div className="text-xs eyebrow" style={{ marginBottom: 4 }}>Your selection</div>
                <div className="row gap-1" style={{ flexWrap: 'wrap', minHeight: 22 }}>
                  {isGeneric ? <Pill kind="accent">Generic portfolio</Pill> :
                   isNone ? <Pill kind="muted">No suitable property — flow stops</Pill> :
                   draftSelection.length === 0 ? (
                     hasSavedSelection
                       ? <span className="text-sm" style={{ color: 'var(--attention)' }}>Nothing selected — saving now removes the approved property.</span>
                       : <span className="text-3 text-sm">Pick at least one property to continue.</span>
                   ) :
                   draftSelection.map((p) => (
                     <Pill key={p} kind="tier1">
                       {propName(p)}
                       {(draftDisciplines[p] || []).length ? ` · ${draftDisciplines[p].join(', ')}` : ''}
                     </Pill>
                   ))}
                </div>
              </div>
              <div className="row gap-2" style={{ flexWrap: 'wrap' }}>
                {/* One-click un-approve — no longer requires picking something else. */}
                {hasSavedSelection && draftSelection.length > 0 ? (
                  <button
                    className="btn"
                    onClick={() => { setDraftSelection([]); setDraftDisciplines({}); }}
                    title="Clear the selection so you can save an empty match and remove the approved property"
                  >
                    <window.I.x size={13} />
                    Remove property
                  </button>
                ) : null}
                <button
                  className="btn btn-primary btn-lg"
                  disabled={confirmDisabled || !changed}
                  onClick={() => onConfirmMatch(selected.id, draftSelection, draftDisciplines)}
                  style={isRemoval ? { background: 'var(--negative)', borderColor: 'var(--negative)' } : undefined}
                >
                  {isRemoval ? 'Remove approved property' :
                   isNone ? 'Confirm — stop flow' :
                   changed ? 'Confirm selection' : 'Selection saved'}
                  {!isNone && !isRemoval ? <window.I.arrowRight size={14} /> : null}
                </button>
              </div>
            </div>

            {/* Where you go next — the flow used to just leave you here. */}
            {!changed && hasSavedSelection ? (
              <div className="row gap-2" style={{ marginTop: 12, paddingTop: 12, borderTop: '1px solid var(--hairline)', flexWrap: 'wrap', alignItems: 'center' }}>
                <window.I.check size={13} stroke="var(--positive)" />
                <span className="text-sm text-2">Saved. Contacts are being found in the background.</span>
                <span className="right row gap-2">
                  <button className="btn btn-sm" onClick={() => onNavigate('contacts', selected.id)}>
                    Go to contacts
                    <window.I.arrowRight size={12} />
                  </button>
                  {nextAwaiting ? (
                    <button className="btn btn-sm btn-primary" onClick={() => onSelectBrand(nextAwaiting.id)}>
                      Next brand: {nextAwaiting.brand}
                      <window.I.arrowRight size={12} />
                    </button>
                  ) : (
                    <>
                      <span className="text-xs text-3">Nothing else here is awaiting a decision.</span>
                      {/* Match is clear — hand the user straight to whatever is next
                          anywhere in the flow rather than leaving them on a dead end. */}
                      <QueueButton brands={brands} onNavigate={onNavigate} className="btn btn-sm btn-primary" />
                    </>
                  )}
                </span>
              </div>
            ) : null}
          </Card>
        </div>
      </div>

      {showOneOff ? (
        <OneOffPropertyModal
          brand={selected}
          onCancel={() => setShowOneOff(false)}
          onSave={handleOneOffCreated}
          onToast={onToast}
        />
      ) : null}
    </div>
  );
}

// ─── Add a property that isn't in the portfolio ───────────────────────────
// Built for the "we have something we can work on short term" case: create it
// here, tagged with its engagement type, and it is selected immediately.
function OneOffPropertyModal({ brand, onCancel, onSave, onToast }) {
  const [form, setForm] = React.useState({
    name: '', short: '', sport: '', rightsholder: '',
    keyMarkets: '', disciplines: '', engagementType: 'one-off', notes: '',
  });
  const [saving, setSaving] = React.useState(false);
  const set = (k, v) => setForm((cur) => ({ ...cur, [k]: v }));
  const canSave = form.name.trim() && form.sport.trim();

  const save = async () => {
    if (!canSave) return;
    setSaving(true);
    const id = form.name.trim().toLowerCase()
      .replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').slice(0, 28)
      + '-' + Date.now().toString(36).slice(-4);
    try {
      await onSave({
        id,
        name: form.name.trim(),
        short: form.short.trim() || form.name.trim(),
        sport: form.sport.trim(),
        rightsholder: form.rightsholder.trim() || form.name.trim(),
        keyMarkets: form.keyMarkets.split(',').map((s) => s.trim()).filter(Boolean),
        disciplines: form.disciplines.split(',').map((s) => s.trim()).filter(Boolean),
        engagementType: form.engagementType,
        audience: { profile: '', reach: '' },
        geographicReach: '',
        existingSponsors: [],
        exclusions: [],
        availableRights: [],
        notes: form.notes.trim(),
        approvalRules: '',
        archived: false,
      });
    } catch (e) {
      console.error('one-off property failed:', e);
      onToast && onToast('Could not add that property');
      setSaving(false);
    }
  };

  const field = (label, key, placeholder) => (
    <div>
      <div className="eyebrow" style={{ marginBottom: 6 }}>{label}</div>
      <input
        value={form[key]}
        onChange={(e) => set(key, e.target.value)}
        placeholder={placeholder}
        style={{
          width: '100%', padding: '8px 12px', border: '1px solid var(--hairline-strong)',
          borderRadius: 6, font: 'inherit', fontSize: 13.5, background: 'var(--card)', color: 'var(--ink)',
        }}
      />
    </div>
  );

  return (
    <div className="drawer-bg" onClick={onCancel}>
      <div className="drawer" onClick={(e) => e.stopPropagation()} style={{ padding: 28 }}>
        <div className="row" style={{ justifyContent: 'space-between', marginBottom: 6 }}>
          <h2 className="h2" style={{ margin: 0 }}>Add a property</h2>
          <button className="btn btn-icon btn-ghost" onClick={onCancel}><window.I.x size={16} /></button>
        </div>
        <div className="text-2 text-sm" style={{ marginBottom: 18, lineHeight: 1.6 }}>
          For something outside the standing portfolio — a one-off event, a short-window asset, or a property
          you have only just picked up. It is saved to the Properties library and selected for {brand.brand} straight away.
        </div>
        <div className="col gap-3">
          {field('Name', 'name', 'e.g. Singapore Padel Open 2027')}
          {field('Short label', 'short', 'e.g. SG Padel')}
          {field('Sport / category', 'sport', 'e.g. Padel — international event')}
          {field('Rightsholder', 'rightsholder', 'Who owns the rights')}
          {field('Key markets (comma separated)', 'keyMarkets', 'Singapore, Malaysia, Indonesia')}
          {field('Disciplines (comma separated, optional)', 'disciplines', 'Men’s singles, Women’s singles')}
          <div>
            <div className="eyebrow" style={{ marginBottom: 6 }}>Engagement type</div>
            <select
              value={form.engagementType}
              onChange={(e) => set('engagementType', e.target.value)}
              className="field"
              style={{ height: 36, padding: '0 10px', fontSize: 13 }}
            >
              {ENGAGEMENT_TYPES.map((t) => <option key={t.value} value={t.value}>{t.label} — {t.desc}</option>)}
            </select>
          </div>
          <div>
            <div className="eyebrow" style={{ marginBottom: 6 }}>Notes</div>
            <textarea
              value={form.notes}
              onChange={(e) => set('notes', e.target.value)}
              rows={3}
              placeholder="Why we can work this, and for how long."
              style={{
                width: '100%', padding: '10px 12px', border: '1px solid var(--hairline-strong)',
                borderRadius: 6, font: 'inherit', fontSize: 13.5, lineHeight: 1.5,
                background: 'var(--card)', color: 'var(--ink)', resize: 'vertical',
              }}
            />
          </div>
        </div>
        <div className="row gap-2" style={{ marginTop: 26, justifyContent: 'flex-end' }}>
          <button className="btn" onClick={onCancel}>Cancel</button>
          <button className="btn btn-primary" disabled={!canSave || saving} onClick={save}>
            {saving ? 'Adding…' : 'Add and select'}
          </button>
        </div>
      </div>
    </div>
  );
}

const scoreDimensions = [
  { key: 'market', label: 'Market overlap' },
  { key: 'audience', label: 'Audience fit' },
  { key: 'values', label: 'Brand values' },
  { key: 'category', label: 'Category fit' },
  { key: 'timing', label: 'Timing' },
];

// Resolve the fit for a brand×property. Prefers the AI-generated scores stored
// on the brand (from the research engine); falls back to a heuristic estimate
// for brands that haven't been researched since fit-scoring was added.
function getFit(brand, prop) {
  const real = brand.fitScores && brand.fitScores[prop.id];
  let total, dimensions, rationale, estimated;

  if (real && real.dimensions) {
    estimated = false;
    total = real.total;
    dimensions = scoreDimensions.map((d) => ({ ...d, value: real.dimensions[d.key] ?? 0 }));
    rationale = real.rationale || '';
  } else {
    const h = estimateFitScore(brand, prop);
    estimated = true;
    total = h.total;
    dimensions = h.dimensions;
    rationale = '';
  }

  // Conflict detection — deterministic, uses structured property/competitor data.
  let conflict = null;
  const exist = (prop.existingSponsors || []).map((s) => String(s && s.sponsor ? s.sponsor : s).toLowerCase());
  const compMatch = (brand.competitors || []).find((c) =>
    exist.some((e) => e.includes(String(c).toLowerCase().split(' ')[0]))
  );
  if (compMatch) conflict = `${compMatch} is an existing ${prop.short} partner`;

  return { total, dimensions, rationale, conflict, estimated };
}

// Heuristic fallback when no AI fit scores exist yet (e.g. legacy seed brands).
// NOTE: deliberately does NOT bonus AI-recommended properties. Inflating their
// score made the app look like it had already picked a winner.
function estimateFitScore(brand, prop) {
  function hash(s) { let h = 0; for (let i = 0; i < s.length; i++) h = (h * 31 + s.charCodeAt(i)) >>> 0; return h; }
  const seed = hash(brand.id + ':' + prop.id);
  const rand = (i) => (((seed * (i + 1) * 9301 + 49297) % 233280) / 233280);

  let total = Math.max(50, (brand.relevanceScore || 60) - 5);
  total = Math.min(96, Math.max(45, total + Math.floor(rand(0) * 10) - 4));

  const dimensions = scoreDimensions.map((d, i) => {
    const base = total + Math.floor(rand(i + 2) * 22) - 10;
    return { ...d, value: Math.min(98, Math.max(40, base)) };
  });
  return { total, dimensions };
}

function MatchCard({ brand, prop, fit, picked, pickedDisciplines, onToggle, onToggleDiscipline }) {
  const isAI = (brand.aiRecommendation?.properties || []).includes(prop.id);
  const markets = prop.keyMarkets || [];
  const disciplines = prop.disciplines || [];
  const engagement = engagementMeta(prop.engagementType);
  const marketNotes = prop.marketNotes || [];
  const [showMarkets, setShowMarkets] = React.useState(false);

  return (
    <Card lg style={{
      borderColor: picked ? 'var(--accent)' : 'var(--hairline)',
      boxShadow: picked ? '0 0 0 1px var(--accent)' : 'var(--shadow-sm)',
      transition: 'all 120ms ease',
      cursor: 'pointer',
    }} onClick={onToggle}>
      <div className="row gap-4" style={{ alignItems: 'flex-start' }}>
        <ScoreRing value={fit.total} size={64} stroke={5} />

        <div className="grow" style={{ minWidth: 0 }}>
          <div className="row gap-2" style={{ marginBottom: 6, flexWrap: 'wrap' }}>
            <span className="h3">{prop.name}</span>
            {prop.engagementType && prop.engagementType !== 'ongoing' ? (
              <Pill kind={engagement.kind} title={engagement.desc}>{engagement.label}</Pill>
            ) : null}
            {isAI ? (
              <Pill kind="accent" className="row gap-1" title="The system suggests this property. It is not selected — you still choose.">
                <window.I.sparkle size={10} />
                AI suggestion
              </Pill>
            ) : null}
            {fit.estimated ? (
              <Pill kind="muted" title="Heuristic estimate — re-research this brand to generate AI fit scores">
                Estimated
              </Pill>
            ) : null}
            <span className="right">
              <button
                className={`btn btn-sm ${picked ? 'btn-primary' : ''}`}
                onClick={(e) => { e.stopPropagation(); onToggle(); }}
                title={picked ? 'Click to unselect' : 'Click to select'}
              >
                {picked ? <><window.I.check size={13} /> Selected</> : 'Select'}
              </button>
            </span>
          </div>

          <div className="row gap-3 text-xs text-3 mono" style={{ marginBottom: 12, flexWrap: 'wrap' }}>
            <span>{prop.sport}</span>
            {markets.length ? (<><span>·</span><span>{markets.slice(0, 3).join(' · ')}</span></>) : null}
          </div>

          {/* Disciplines — tag the specific one (e.g. FEI → Dressage) */}
          {disciplines.length ? (
            <div style={{ marginBottom: 14 }} onClick={(e) => e.stopPropagation()}>
              <div className="eyebrow text-xs" style={{ marginBottom: 6 }}>
                Disciplines — tag the ones in play
              </div>
              <div className="row gap-1" style={{ flexWrap: 'wrap' }}>
                {disciplines.map((d) => {
                  const on = (pickedDisciplines || []).includes(d);
                  return (
                    <button
                      key={d}
                      type="button"
                      className="pill"
                      onClick={() => onToggleDiscipline(d)}
                      style={{
                        cursor: 'pointer', fontSize: 11, padding: '4px 9px',
                        background: on ? 'var(--accent)' : 'transparent',
                        color: on ? 'white' : 'var(--ink-2)',
                        border: '1px solid ' + (on ? 'var(--accent)' : 'var(--hairline-strong)'),
                      }}
                    >
                      {on ? '✓ ' : ''}{d}
                    </button>
                  );
                })}
              </div>
            </div>
          ) : null}

          {/* Scoring breakdown */}
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(90px, 1fr))', gap: 16, marginBottom: 14 }}>
            {fit.dimensions.map((d) => (
              <div key={d.key} className="col gap-1">
                <span className="eyebrow text-xs">{d.label}</span>
                <ScoreBar value={d.value} />
              </div>
            ))}
          </div>

          {/* Why fits */}
          {fit.rationale ? (
            <div style={{ padding: 12, background: 'var(--card-alt)', borderRadius: 8, fontSize: 13, lineHeight: 1.55, color: 'var(--ink-2)' }}>
              <div className="eyebrow" style={{ marginBottom: 4 }}>Why this fits</div>
              {fit.rationale}
            </div>
          ) : (
            <div style={{ padding: 12, background: 'var(--card-alt)', borderRadius: 8, fontSize: 13, lineHeight: 1.55, color: 'var(--ink-2)' }}>
              <div className="eyebrow" style={{ marginBottom: 4 }}>Why this could fit</div>
              {prop.notes || `${prop.name} maps against ${brand.brand}'s market and audience profile.`}
            </div>
          )}

          {/* Geography — full detail in the card, so nobody has to google it */}
          {(markets.length || prop.geographicReach || marketNotes.length) ? (
            <div style={{ marginTop: 12 }} onClick={(e) => e.stopPropagation()}>
              <button
                className="row gap-2"
                onClick={() => setShowMarkets((x) => !x)}
                style={{ background: 'transparent', border: 0, cursor: 'pointer', padding: 0, alignItems: 'center' }}
              >
                <span className="eyebrow text-xs row gap-1">
                  <window.I.globe size={11} stroke="var(--ink-3)" />
                  Geographic markets ({markets.length})
                </span>
                <window.I.chevronDown
                  size={13}
                  stroke="var(--ink-3)"
                  style={{ transform: showMarkets ? 'rotate(180deg)' : 'none', transition: 'transform 120ms' }}
                />
              </button>
              {showMarkets ? (
                <div className="col gap-2" style={{ marginTop: 8, padding: 12, background: 'var(--card-alt)', borderRadius: 8 }}>
                  {markets.length ? (
                    <div className="row gap-1" style={{ flexWrap: 'wrap' }}>
                      {markets.map((m, i) => <Pill key={i} kind="muted">{m}</Pill>)}
                    </div>
                  ) : null}
                  {prop.geographicReach ? (
                    <div className="text-sm text-2" style={{ lineHeight: 1.55 }}>{prop.geographicReach}</div>
                  ) : null}
                  {marketNotes.length ? (
                    <div className="col gap-1" style={{ marginTop: 2 }}>
                      {marketNotes.map((m, i) => (
                        <div key={i} className="text-xs text-2" style={{ lineHeight: 1.5 }}>
                          <strong>{m.market}</strong>{m.why ? ` — ${m.why}` : ''}
                        </div>
                      ))}
                    </div>
                  ) : null}
                  {!prop.geographicReach && !marketNotes.length ? (
                    <div className="text-xs text-3">
                      No geographic detail on file yet. Open Properties → {prop.short || prop.name} → Research to fill this in.
                    </div>
                  ) : null}
                </div>
              ) : null}
            </div>
          ) : null}

          {/* Commercial angle + risks */}
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(160px, 1fr))', gap: 12, marginTop: 12 }}>
            <div>
              <div className="eyebrow text-xs" style={{ marginBottom: 6 }}>Available rights</div>
              <div className="row gap-1" style={{ flexWrap: 'wrap' }}>
                {(prop.availableRights || []).slice(0, 4).map((r, i) => (
                  <Pill key={i} kind="muted">{r}</Pill>
                ))}
              </div>
            </div>
            <div>
              <div className="eyebrow text-xs row gap-1" style={{ marginBottom: 6 }}>
                <window.I.warn size={11} />
                Risks & considerations
              </div>
              {fit.conflict ? (
                <div className="text-sm" style={{ color: 'var(--attention)' }}>{fit.conflict}.</div>
              ) : prop.exclusions && prop.exclusions.length ? (
                <div className="text-2 text-sm">No active conflict. Watch: {prop.exclusions[0]}.</div>
              ) : (
                <div className="text-3 text-sm">No active conflicts detected.</div>
              )}
            </div>
          </div>
        </div>
      </div>
    </Card>
  );
}

window.PropertyMatch = PropertyMatch;
