// ─── Chloe — AI Chief Brand Officer ─────────────────────
// Extensible context providers + tool executors architecture

// ─── Profile Image Cache ────────────────────────────────
var _chloeProfileImage = null;

function getChloeProfileImage() {
  if (_chloeProfileImage) return Promise.resolve(_chloeProfileImage);
  return window.CanvasRenderer.loadImage('/engine/profile.png').then(function(img) {
    _chloeProfileImage = img;
    return img;
  }).catch(function() { return null; });
}

// ─── Context Providers ──────────────────────────────────
var CHLOE_PROVIDERS = {
  drops: function(props) {
    if (!props.drops) return null;
    var allDrops = Object.values(props.drops);
    var allSlips = props.slips ? Object.values(props.slips) : [];
    var now = new Date();
    var todayStr = now.toISOString().split('T')[0];
    var windowStart = new Date(now);
    windowStart.setDate(windowStart.getDate() - 7);
    var windowEnd = new Date(now);
    windowEnd.setDate(windowEnd.getDate() + 30);
    var startStr = windowStart.toISOString().split('T')[0];
    var endStr = windowEnd.toISOString().split('T')[0];

    // Build calendar with summarized entries (drops + slips)
    var scheduled = allDrops.filter(function(d) {
      return d.scheduled_date && d.scheduled_date >= startStr && d.scheduled_date <= endStr;
    });
    var scheduledSlips = allSlips.filter(function(s) {
      return s.scheduled_date && s.scheduled_date >= startStr && s.scheduled_date <= endStr;
    });
    var byDate = {};
    scheduled.forEach(function(d) {
      if (!byDate[d.scheduled_date]) byDate[d.scheduled_date] = [];
      byDate[d.scheduled_date].push({ id: d.id, type: 'drop', line: d.line, source: d.source, medium: d.medium, status: d.status, has_media: !!d.clip_url });
    });
    scheduledSlips.forEach(function(s) {
      if (!byDate[s.scheduled_date]) byDate[s.scheduled_date] = [];
      byDate[s.scheduled_date].push({ id: s.id, type: 'slip', acronym: s.acronym, answer: s.answer, source: s.source, medium: s.medium, status: s.status });
    });

    // Gap analysis — find dates in upcoming 30 days with no content (drops or slips)
    var gaps = [];
    var stacked = [];
    var cursor = new Date(todayStr + 'T00:00:00');
    for (var i = 0; i < 30; i++) {
      var dateStr = cursor.toISOString().split('T')[0];
      var count = byDate[dateStr] ? byDate[dateStr].length : 0;
      if (count === 0) gaps.push(dateStr);
      if (count >= 2) stacked.push({ date: dateStr, count: count });
      cursor.setDate(cursor.getDate() + 1);
    }

    // Medium distribution in window
    var mediumDist = {};
    scheduled.forEach(function(d) {
      mediumDist[d.medium] = (mediumDist[d.medium] || 0) + 1;
    });

    // Consecutive same-medium detection
    var sortedScheduled = scheduled.slice().sort(function(a, b) { return a.scheduled_date.localeCompare(b.scheduled_date); });
    var consecutiveSameMedium = [];
    for (var j = 1; j < sortedScheduled.length; j++) {
      if (sortedScheduled[j].medium === sortedScheduled[j-1].medium) {
        var d1 = new Date(sortedScheduled[j-1].scheduled_date + 'T00:00:00');
        var d2 = new Date(sortedScheduled[j].scheduled_date + 'T00:00:00');
        if ((d2 - d1) / 86400000 <= 1) {
          consecutiveSameMedium.push({
            medium: sortedScheduled[j].medium,
            dates: [sortedScheduled[j-1].scheduled_date, sortedScheduled[j].scheduled_date]
          });
        }
      }
    }

    // Missing media in upcoming drops
    var missingMedia = scheduled.filter(function(d) {
      return d.scheduled_date >= todayStr && !byDate[d.scheduled_date][0].has_media;
    }).length;

    // Performance stats
    var published = allDrops.filter(function(d) { return d.status === 'published'; });
    var fire = published.filter(function(d) { return d.performance === 'fire'; }).length;
    var mid = published.filter(function(d) { return d.performance === 'mid'; }).length;
    var flop = published.filter(function(d) { return d.performance === 'flop'; }).length;

    // Top 5 performers with pattern info
    var topFive = allDrops.filter(function(d) { return d.performance === 'fire'; })
      .slice(-5).map(function(d) { return { line: d.line, source: d.source, medium: d.medium, situation: d.situation }; });

    // Recent flops for anti-pattern learning
    var recentFlops = allDrops.filter(function(d) { return d.performance === 'flop'; })
      .slice(-3).map(function(d) { return { line: d.line, source: d.source, medium: d.medium, situation: d.situation }; });

    return {
      total: allDrops.length,
      calendarWindow: startStr + ' to ' + endStr,
      today: todayStr,
      calendar: byDate,
      analysis: {
        gapDates: gaps.length > 10 ? gaps.slice(0, 10).concat(['... ' + (gaps.length - 10) + ' more']) : gaps,
        gapCount: gaps.length,
        stackedDates: stacked,
        mediumDistribution: mediumDist,
        consecutiveSameMedium: consecutiveSameMedium,
        missingMediaCount: missingMedia
      },
      performance: { fire: fire, mid: mid, flop: flop, unrated: published.length - fire - mid - flop },
      topPerformers: topFive,
      recentFlops: recentFlops
    };
  },

  slips: function(props) {
    if (!props.slips) return null;
    var allSlips = Object.values(props.slips);
    var mediums = {};
    allSlips.forEach(function(s) { mediums[s.medium] = (mediums[s.medium] || 0) + 1; });
    var recent = allSlips.slice().sort(function(a, b) { return (b.created_at || 0) - (a.created_at || 0); }).slice(0, 5);

    var draft = allSlips.filter(function(s) { return s.status === 'draft'; }).length;
    var scheduled = allSlips.filter(function(s) { return s.status === 'scheduled'; }).length;
    var published = allSlips.filter(function(s) { return s.status === 'published'; }).length;
    var fire = allSlips.filter(function(s) { return s.performance === 'fire'; }).length;
    var mid = allSlips.filter(function(s) { return s.performance === 'mid'; }).length;
    var flop = allSlips.filter(function(s) { return s.performance === 'flop'; }).length;
    var unrated = allSlips.filter(function(s) { return s.status === 'published' && !s.performance; }).length;

    return {
      total: allSlips.length,
      byMedium: mediums,
      byStatus: { draft: draft, scheduled: scheduled, published: published },
      performance: { fire: fire, mid: mid, flop: flop, unrated: unrated },
      recent: recent.map(function(s) { return { acronym: s.acronym, answer: s.answer, source: s.source, medium: s.medium, status: s.status, scheduled_date: s.scheduled_date }; })
    };
  },

  brand: function(props) {
    return {
      tasteProfile: props.tasteProfile && props.tasteProfile.content ? props.tasteProfile.content : null,
      brandSettings: props.brandSettings || null
    };
  },

  library: function(props) {
    if (!props.cards) return null;
    var cardArray = Object.values(props.cards);

    var byMedia = {};
    var byDiff = {};
    var staged = 0;
    var review = 0;
    var totalScore = 0;
    var scored = 0;

    cardArray.forEach(function(c) {
      byMedia[c.media_type] = (byMedia[c.media_type] || 0) + 1;
      byDiff[c.difficulty] = (byDiff[c.difficulty] || 0) + 1;
      if (c.staged) staged++;
      if (c.status === 'review') review++;
      if (c.scores && c.scores.lineconic_score > 0) {
        totalScore += c.scores.lineconic_score;
        scored++;
      }
    });

    var recent = cardArray.slice(-10).map(function(c) {
      return { id: c.id, source: c.source, media_type: c.media_type, difficulty: c.difficulty, score: c.scores ? c.scores.lineconic_score : 0 };
    });

    var topCrowd = [];
    if (props.crowdScores) {
      topCrowd = cardArray
        .filter(function(c) { return props.crowdScores[c.id] && props.crowdScores[c.id].crowd_score > 0; })
        .sort(function(a, b) { return (props.crowdScores[b.id].crowd_score || 0) - (props.crowdScores[a.id].crowd_score || 0); })
        .slice(0, 10)
        .map(function(c) {
          return { id: c.id, answer: c.content ? c.content.answer : '', source: c.source, crowd_score: props.crowdScores[c.id].crowd_score, shows_played: props.crowdScores[c.id].showCount };
        });
    }

    return {
      total: cardArray.length,
      staged: staged,
      flaggedForReview: review,
      byMediaType: byMedia,
      byDifficulty: byDiff,
      avgLineconicScore: scored > 0 ? (totalScore / scored).toFixed(1) : '0',
      scoredCount: scored,
      recent: recent,
      topByAudience: topCrowd
    };
  },

  shows: function(props) {
    if (!props.showIndex) return null;
    var shows = Object.entries(props.showIndex);
    var recent = shows
      .sort(function(a, b) { return (b[1].created_at || '').localeCompare(a[1].created_at || ''); })
      .slice(0, 5)
      .map(function(entry) {
        return { id: entry[0], name: entry[1].name, format: entry[1].format, created_at: entry[1].created_at };
      });

    return {
      total: shows.length,
      activeShow: props.activeShow || null,
      recent: recent
    };
  },

  creators: function(props) {
    if (!props.creators) return null;
    var arr = Object.values(props.creators);

    var creatorDrops = {};
    if (props.drops) {
      Object.values(props.drops).forEach(function(d) {
        if (d.creator_id) {
          if (!creatorDrops[d.creator_id]) creatorDrops[d.creator_id] = { total: 0, fire: 0, mid: 0, flop: 0 };
          creatorDrops[d.creator_id].total++;
          if (d.performance === 'fire') creatorDrops[d.creator_id].fire++;
          if (d.performance === 'mid') creatorDrops[d.creator_id].mid++;
          if (d.performance === 'flop') creatorDrops[d.creator_id].flop++;
        }
      });
    }

    var summary = arr.map(function(c) {
      var stats = creatorDrops[c.id] || { total: 0, fire: 0, mid: 0, flop: 0 };
      return {
        id: c.id,
        name: c.name,
        handle: c.handle,
        platform: c.platform,
        audience_size: c.audience_size,
        audience_demo: c.audience_demo,
        content_style: c.content_style,
        drops_assigned: stats.total,
        fire_rate: stats.total > 0 ? Math.round(stats.fire / stats.total * 100) : 0
      };
    });

    return {
      total: arr.length,
      creators: summary
    };
  }
};

// ─── Tool Executors ─────────────────────────────────────
var CHLOE_EXECUTORS = {
  create_drops: function(data, db, props) {
    var updates = {};
    var count = 0;
    (data.drops || []).forEach(function(drop) {
      var id = 'drop-' + Date.now() + '-' + Math.random().toString(36).substr(2, 6);
      var entry = {
        id: id,
        line: drop.line || '',
        source: drop.source || '',
        situation: drop.situation || '',
        acronym: drop.acronym || '',
        signoff: drop.signoff || drop.acronym || '',
        medium: drop.medium || 'movie',
        scheduled_date: drop.scheduled_date || null,
        status: 'scheduled',
        show_profile: true,
        radius: 10,
        video_scale: 1.0,
        created_at: new Date().toISOString(),
        created_by: 'chloe'
      };
      if (drop.line) {
        entry.reference_id = generateId(drop.line);
      }
      if (drop.creator_id) {
        entry.creator_id = drop.creator_id;
      }
      updates['drops/' + id] = entry;
      count++;
    });
    return db.ref().update(updates).then(function() { return count + ' drops created'; });
  },

  create_slips: function(data, db) {
    var updates = {};
    var count = 0;
    (data.slips || []).forEach(function(slip) {
      var id = 'slip-' + Date.now() + '-' + Math.random().toString(36).substr(2, 6);
      updates['slips/' + id] = {
        id: id,
        acronym: (slip.acronym || '').toUpperCase(),
        answer: (slip.answer || '').toUpperCase(),
        source: slip.source || '',
        medium: slip.medium || 'film',
        setup: slip.setup || '',
        signoff: typeof slip.signoff === 'number' ? slip.signoff : 0,
        created_at: Date.now(),
        created_by: 'chloe',
        scheduled_date: slip.scheduled_date || null,
        status: slip.scheduled_date ? 'scheduled' : 'draft',
        performance: null,
        rated_at: null
      };
      count++;
    });
    return db.ref().update(updates).then(function() { return count + ' slips created'; });
  },

  move_slips: function(data, db) {
    var updates = {};
    var count = 0;
    (data.moves || []).forEach(function(move) {
      if (move.slip_id && move.new_date) {
        updates['slips/' + move.slip_id + '/scheduled_date'] = move.new_date;
        updates['slips/' + move.slip_id + '/status'] = 'scheduled';
        count++;
      }
    });
    if (count === 0) return Promise.resolve('no slips to move');
    return db.ref().update(updates).then(function() { return count + ' slips rescheduled'; });
  },

  move_drops: function(data, db) {
    var updates = {};
    var count = 0;
    (data.moves || []).forEach(function(move) {
      if (move.drop_id && move.new_date) {
        updates['drops/' + move.drop_id + '/scheduled_date'] = move.new_date;
        count++;
      }
    });
    if (count === 0) return Promise.resolve('no drops to move');
    return db.ref().update(updates).then(function() { return count + ' drops rescheduled'; });
  },

  review_calendar: function(data) {
    return Promise.resolve('Calendar review is handled by the AI — no client action needed.');
  },

  check_media: function(data) {
    return Promise.resolve('Media check is handled by the AI — no client action needed.');
  },

  build_setlist: function(data, db, props) {
    return firebase.auth().currentUser.getIdToken().then(function(token) {
      var template = props.templates ? props.templates[data.template_id] : { id: data.template_id };
      var availableCards = props.library || (props.cards ? Object.values(props.cards) : []);
      return aiFetch('/api/ai/optimize-setlist', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
        body: JSON.stringify({
          template: template || { id: data.template_id },
          venue: data.constraints || '',
          audience: data.show_name,
          availableCards: availableCards
        })
      });
    }).then(function(res) {
      return res.json();
    }).then(function(result) {
      if (result.suggestions) {
        return 'setlist built: ' + data.show_name + ' (' + data.template_id + '). strategy: ' + (result.suggestions.strategy || 'complete');
      }
      return 'setlist optimization complete';
    });
  },

  audit_library: function(data) {
    return Promise.resolve('Library audit is handled by the AI from context data -- no client action needed.');
  },

  write_captions: function(data, db) {
    var updates = {};
    var count = 0;
    (data.drops || []).forEach(function(item) {
      if (item.drop_id && item.caption) {
        updates['drops/' + item.drop_id + '/caption'] = item.caption;
        updates['drops/' + item.drop_id + '/caption_platform'] = item.platform || 'instagram';
        count++;
      }
    });
    if (count === 0) return Promise.resolve('no captions to write');
    return db.ref().update(updates).then(function() { return count + ' captions written'; });
  }
};

// ─── Context Builder ────────────────────────────────────
function buildChloeContext(props) {
  var context = {};
  Object.keys(CHLOE_PROVIDERS).forEach(function(key) {
    try {
      var result = CHLOE_PROVIDERS[key](props);
      if (result) context[key] = result;
    } catch(e) {
      console.warn('[chloe] provider error:', key, e);
    }
  });
  return context;
}

// ─── Mini Calendar Preview ──────────────────────────────
function ChloeCalendarPreview(props) {
  var drops = props.drops || [];
  var slipItems = props.slips || [];
  var allItems = drops.concat(slipItems);
  if (allItems.length === 0) return null;

  // Find month range from all items
  var dates = allItems.map(function(d) { return d.scheduled_date; }).filter(Boolean).sort();
  if (dates.length === 0) return null;

  var first = new Date(dates[0] + 'T00:00:00');
  var year = first.getFullYear();
  var month = first.getMonth();
  var months = ['JAN','FEB','MAR','APR','MAY','JUN','JUL','AUG','SEP','OCT','NOV','DEC'];

  var firstDay = new Date(year, month, 1).getDay();
  var daysInMonth = new Date(year, month + 1, 0).getDate();
  var days = [];
  for (var i = 0; i < firstDay; i++) days.push(null);
  for (var d = 1; d <= daysInMonth; d++) days.push(d);

  // Map all items by date
  var dropsByDate = {};
  allItems.forEach(function(item) {
    if (!item.scheduled_date) return;
    if (!dropsByDate[item.scheduled_date]) dropsByDate[item.scheduled_date] = [];
    dropsByDate[item.scheduled_date].push(item);
  });

  var mediumColors = {
    movie: 'var(--signal)', music: 'var(--cyan)', tv: 'var(--gold)',
    internet: '#4ade80', ad: '#a78bfa', meme: '#f97316', cliche: '#94a3b8'
  };

  return (
    <div className="CH-calendar-mini">
      <div style={{fontSize:10,color:'var(--text-sec)',letterSpacing:'.08em',marginBottom:8}}>{months[month]} {year}</div>
      <div style={{display:'grid',gridTemplateColumns:'repeat(7,1fr)',gap:2,fontSize:9}}>
        {['S','M','T','W','T','F','S'].map(function(d,i) {
          return <div key={i} style={{textAlign:'center',color:'var(--text-dim)',padding:2}}>{d}</div>;
        })}
        {days.map(function(day, idx) {
          if (day === null) return <div key={'e'+idx}></div>;
          var dateStr = year + '-' + String(month + 1).padStart(2, '0') + '-' + String(day).padStart(2, '0');
          var dayDrops = dropsByDate[dateStr];
          return (
            <div key={idx} className="CH-cal-day" style={{
              textAlign:'center', padding:3,
              background: dayDrops ? 'rgba(255,0,127,.12)' : 'transparent',
              border: dayDrops ? '1px solid rgba(255,0,127,.3)' : '1px solid transparent'
            }}>
              <div style={{color: dayDrops ? '#fff' : 'var(--text-dim)'}}>{day}</div>
              {dayDrops && dayDrops.map(function(drop, di) {
                return <div key={di} style={{
                  width:6, height:6, borderRadius:'50%',
                  background: mediumColors[drop.medium] || 'var(--signal)',
                  margin:'1px auto 0', display:'inline-block'
                }}></div>;
              })}
            </div>
          );
        })}
      </div>
    </div>
  );
}

// ─── Action Card ────────────────────────────────────────
function ChloeActionCard(props) {
  var toolCall = props.toolCall;
  var onAccept = props.onAccept;
  var onReject = props.onReject;
  var accepted = props.accepted;
  var brandSettings = props.brandSettings;

  var isMobile = props.isMobile;
  var name = toolCall.name;
  var input = toolCall.input;

  var [previews, setPreviews] = useState({});
  var [renderUrls, setRenderUrls] = useState({});
  var [rendering, setRendering] = useState({});

  // Cleanup blob URLs on unmount
  useEffect(function() {
    return function() {
      Object.values(renderUrls).forEach(function(url) {
        try { URL.revokeObjectURL(url); } catch(e) {}
      });
    };
  }, []);

  function previewDrop(drop, i) {
    var mediaUrl = drop.clip_url || drop.media_url || '';
    var loadMedia = mediaUrl
      ? window.CanvasRenderer.loadImage(mediaUrl).catch(function() { return null; })
      : Promise.resolve(null);

    Promise.all([getChloeProfileImage(), loadMedia]).then(function(results) {
      var profileImg = results[0];
      var mediaEl = results[1];
      var settings = Object.assign({}, brandSettings || {}, {
        show_profile: true,
        show_footer: true
      });
      return window.CanvasRenderer.renderLayoutToCanvas(
        { setup: drop.situation || '', puzzle: drop.signoff || '' },
        settings,
        { profileImage: profileImg, mediaElement: mediaEl }
      );
    }).then(function(result) {
      var dataUrl = result.canvas.toDataURL('image/jpeg', 0.9);
      setPreviews(function(prev) { var n = Object.assign({}, prev); n[i] = dataUrl; return n; });
    }).catch(function(e) {
      console.warn('[chloe] preview failed:', e);
    });
  }

  function renderDropToMP4(drop, i) {
    setRendering(function(prev) { var n = Object.assign({}, prev); n[i] = 0; return n; });
    var mediaUrl = drop.clip_url || drop.media_url || '';
    var settings = Object.assign({}, brandSettings || {}, {
      show_profile: true,
      show_footer: true
    });
    window.FFmpegRenderer.renderToMP4(
      { setup: drop.situation || '', puzzle: drop.signoff || '', mediaUrl: mediaUrl },
      settings,
      function(pct) {
        setRendering(function(prev) { var n = Object.assign({}, prev); n[i] = pct; return n; });
      }
    ).then(function(blob) {
      var url = URL.createObjectURL(blob);
      setRenderUrls(function(prev) { var n = Object.assign({}, prev); n[i] = url; return n; });
      setRendering(function(prev) { var n = Object.assign({}, prev); n[i] = null; return n; });
    }).catch(function(e) {
      console.warn('[chloe] render failed:', e);
      setRendering(function(prev) { var n = Object.assign({}, prev); n[i] = null; return n; });
    });
  }

  function downloadDrop(drop, i) {
    if (!renderUrls[i]) return;
    var a = document.createElement('a');
    a.href = renderUrls[i];
    var filename = (drop.line || 'drop').replace(/[^a-zA-Z0-9 ]/g, '').replace(/\s+/g, '-').substring(0, 50);
    a.download = filename + '.mp4';
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
  }

  var labels = {
    create_drops: 'CREATE DROPS',
    create_slips: 'CREATE SLIPS',
    review_calendar: 'REVIEW CALENDAR',
    check_media: 'CHECK MEDIA',
    move_drops: 'RESCHEDULE DROPS',
    move_slips: 'RESCHEDULE SLIPS',
    build_setlist: 'BUILD SETLIST',
    audit_library: 'AUDIT LIBRARY',
    write_captions: 'WRITE CAPTIONS'
  };

  var items = name === 'create_drops' ? (input.drops || []) : name === 'create_slips' ? (input.slips || []) : [];

  return (
    <div className="CH-action-card">
      <div style={{display:'flex',justifyContent:'space-between',alignItems:'center',marginBottom:8}}>
        <span style={{fontSize:10,color:'var(--gold)',letterSpacing:'.1em',textTransform:'uppercase'}}>{labels[name] || name}</span>
        <span style={{fontSize:10,color:'var(--text-dim)'}}>{items.length > 0 ? items.length + ' items' : ''}</span>
      </div>

      {name === 'create_drops' && items.length > 0 && (
        <div>
          {!isMobile && <ChloeCalendarPreview drops={items} />}
          <div style={{marginTop:8}}>
            {items.map(function(drop, i) {
              return (
                <div key={i} style={{padding:'6px 0',borderBottom:'1px solid rgba(42,42,42,.4)',fontSize:11}}>
                  <div style={{color:'#fff'}}>"{drop.line}" <span style={{color:'var(--text-sec)'}}>— {drop.source}</span></div>
                  <div style={{color:'var(--cyan)',marginTop:2}}>{drop.situation}</div>
                  <div style={{display:'flex',gap:12,fontSize:10,color:'var(--text-dim)',marginTop:2,flexWrap:'wrap'}}>
                    <span>{drop.medium}</span>
                    <span>{drop.scheduled_date}</span>
                    <span>"{drop.signoff}"</span>
                  </div>
                  <div style={{display:'flex',gap:6,marginTop:6,alignItems:'center'}}>
                    {!previews[i] && (
                      <button className="A-btn" style={{fontSize:9,padding:'2px 8px'}} onClick={function() { previewDrop(drop, i); }}>PREVIEW</button>
                    )}
                    {previews[i] && !renderUrls[i] && rendering[i] == null && (
                      <button className="A-btn" style={{fontSize:9,padding:'2px 8px'}} onClick={function() { renderDropToMP4(drop, i); }}>RENDER MP4</button>
                    )}
                    {rendering[i] != null && (
                      <span style={{fontSize:9,color:'var(--gold)',letterSpacing:'.06em'}}>{rendering[i]}%</span>
                    )}
                    {renderUrls[i] && (
                      <button className="A-btn signal" style={{fontSize:9,padding:'2px 8px'}} onClick={function() { downloadDrop(drop, i); }}>DOWNLOAD</button>
                    )}
                  </div>
                  {previews[i] && !renderUrls[i] && (
                    <img src={previews[i]} style={{maxWidth:'100%',width:'100%',marginTop:6,borderRadius:4,display:'block'}} />
                  )}
                  {renderUrls[i] && (
                    <video src={renderUrls[i]} controls style={{maxWidth:'100%',width:'100%',marginTop:6,borderRadius:4,display:'block'}} />
                  )}
                </div>
              );
            })}
          </div>
        </div>
      )}

      {(name === 'move_drops' || name === 'move_slips') && input.moves && input.moves.length > 0 && (
        <div style={{marginTop:4}}>
          {input.moves.map(function(move, i) {
            return (
              <div key={i} style={{padding:'4px 0',borderBottom:'1px solid rgba(42,42,42,.4)',fontSize:11,color:'var(--text-sec)'}}>
                <span style={{color:'var(--text-dim)'}}>{move.drop_id}</span>
                <span style={{margin:'0 8px'}}>→</span>
                <span style={{color:'var(--cyan)'}}>{move.new_date}</span>
              </div>
            );
          })}
        </div>
      )}

      {name === 'create_slips' && items.length > 0 && (
        <div style={{marginTop:4}}>
          {items.map(function(slip, i) {
            return (
              <div key={i} style={{padding:'6px 0',borderBottom:'1px solid rgba(42,42,42,.4)',fontSize:11}}>
                <span style={{fontFamily:'var(--display)',fontSize:13,color:'#fff',letterSpacing:'.04em'}}>{slip.acronym}</span>
                <span style={{color:'var(--gold)',marginLeft:8}}>{slip.answer}</span>
                <div style={{fontSize:10,color:'var(--text-dim)',marginTop:2}}>{slip.source} — {slip.medium}</div>
              </div>
            );
          })}
        </div>
      )}

      {name === 'build_setlist' && (
        <div style={{marginTop:4,fontSize:11}}>
          <div style={{color:'var(--cyan)'}}>Template: {input.template_id}</div>
          <div style={{color:'#fff',marginTop:4}}>Show: {input.show_name}</div>
          {input.constraints && <div style={{color:'var(--text-sec)',marginTop:4}}>{input.constraints}</div>}
        </div>
      )}

      {name === 'write_captions' && (input.drops || []).length > 0 && (
        <div style={{marginTop:4}}>
          {(input.drops || []).map(function(item, i) {
            return (
              <div key={i} style={{padding:'6px 0',borderBottom:'1px solid rgba(42,42,42,.4)',fontSize:11}}>
                <div style={{color:'var(--text-dim)',fontSize:10}}>{item.drop_id} ({item.platform})</div>
                <div style={{color:'#fff',marginTop:2}}>{item.caption}</div>
              </div>
            );
          })}
        </div>
      )}

      {!accepted && (
        <div style={{display:'flex',gap:8,marginTop:12}}>
          <button className="A-btn signal" onClick={function() { onAccept(toolCall); }}>ACCEPT</button>
          <button className="A-btn" onClick={function() { onReject(toolCall); }}>REJECT</button>
        </div>
      )}
      {accepted === true && (
        <div style={{marginTop:8,fontSize:10,color:'#4ade80',letterSpacing:'.06em'}}>ACCEPTED</div>
      )}
      {accepted === 'rejected' && (
        <div style={{marginTop:8,fontSize:10,color:'var(--signal)',letterSpacing:'.06em'}}>REJECTED</div>
      )}
    </div>
  );
}

// ─── Context Nudges ─────────────────────────────────────
function ChloeContextNudges(props) {
  var nudges = [];

  var now = new Date();
  var todayStr = now.toISOString().split('T')[0];
  var nextWeekEnd = new Date(now);
  nextWeekEnd.setDate(nextWeekEnd.getDate() + 7);
  var endStr = nextWeekEnd.toISOString().split('T')[0];

  if (props.drops) {
    var scheduledDrops = Object.values(props.drops).filter(function(d) {
      return d.scheduled_date && d.scheduled_date >= todayStr && d.scheduled_date <= endStr;
    });
    var scheduledSlips = props.slips ? Object.values(props.slips).filter(function(s) {
      return s.scheduled_date && s.scheduled_date >= todayStr && s.scheduled_date <= endStr;
    }) : [];
    var contentDates = {};
    scheduledDrops.forEach(function(d) { contentDates[d.scheduled_date] = true; });
    scheduledSlips.forEach(function(s) { contentDates[s.scheduled_date] = true; });
    var gapDays = 7 - Object.keys(contentDates).length;
    if (gapDays > 2) {
      nudges.push(gapDays + ' days with no content in the next 7');
    }

    var unratedDrops = Object.values(props.drops).filter(function(d) {
      return d.status === 'published' && !d.performance;
    });
    if (unratedDrops.length > 3) {
      nudges.push(unratedDrops.length + ' published drops unrated');
    }

    var noMedia = Object.values(props.drops).filter(function(d) {
      return d.scheduled_date && d.scheduled_date >= todayStr && !d.clip_url;
    });
    if (noMedia.length > 0) {
      nudges.push(noMedia.length + ' upcoming drops missing media');
    }
  }

  if (props.slips) {
    var unratedSlips = Object.values(props.slips).filter(function(s) {
      return s.status === 'published' && !s.performance;
    });
    if (unratedSlips.length > 3) {
      nudges.push(unratedSlips.length + ' published slips unrated');
    }
  }

  if (nudges.length === 0) return null;

  return (
    <div style={{maxWidth:'90%',width:'100%',padding:'8px 0'}}>
      {nudges.map(function(n, i) {
        return (
          <div key={i} style={{fontSize:10,color:'var(--signal)',padding:'3px 0',textAlign:'center',letterSpacing:'.04em'}}>
            {n}
          </div>
        );
      })}
    </div>
  );
}

// ─── Main Module ────────────────────────────────────────
function ChloeModule(props) {
  var db = props.db, showToast = props.showToast;

  var [isMobile, setIsMobile] = useState(window.innerWidth < 768);
  useEffect(function(){
    function onResize(){setIsMobile(window.innerWidth < 768)}
    window.addEventListener('resize', onResize);
    return function(){window.removeEventListener('resize', onResize)};
  },[]);

  var [messages, setMessages] = useState([]);
  var [input, setInput] = useState('');
  var [loading, setLoading] = useState(false);
  var [acceptedTools, setAcceptedTools] = useState({});
  var [sessionId, setSessionId] = useState(null);
  var messagesEndRef = useRef(null);
  var inputRef = useRef(null);

  function getToken() { return firebase.auth().currentUser.getIdToken(); }

  // Auto-scroll to bottom
  useEffect(function() {
    if (messagesEndRef.current) {
      messagesEndRef.current.scrollIntoView({ behavior: 'smooth' });
    }
  }, [messages, loading]);

  // Cmd+K / Ctrl+K to focus input from anywhere
  useEffect(function() {
    function handleGlobalKey(e) {
      if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
        e.preventDefault();
        if (inputRef.current) inputRef.current.focus();
      }
    }
    document.addEventListener('keydown', handleGlobalKey);
    return function() { document.removeEventListener('keydown', handleGlobalKey); };
  }, []);

  // Load most recent session on mount
  useEffect(function() {
    if (!props.user) return;
    var ref = db.ref('chloe_sessions/' + props.user.uid);
    ref.orderByChild('updated_at').limitToLast(1).once('value').then(function(snap) {
      var sessions = snap.val();
      if (sessions) {
        var id = Object.keys(sessions)[0];
        var session = sessions[id];
        if (session.messages && session.messages.length > 0) {
          setSessionId(id);
          setMessages(session.messages);
          // Restore tool decisions from saved messages
          var restored = {};
          session.messages.forEach(function(msg) {
            if (msg.toolCalls) {
              msg.toolCalls.forEach(function(tc) {
                if (tc.decision) restored[tc.id] = tc.decision;
              });
            }
          });
          if (Object.keys(restored).length > 0) setAcceptedTools(restored);
        }
      }
    }).catch(function(e) {
      console.warn('[chloe] session load failed:', e.message);
    });
  }, [props.user]);

  // Auto-save after each message
  useEffect(function() {
    if (!props.user || messages.length === 0) return;
    var id = sessionId || ('session-' + Date.now());
    if (!sessionId) setSessionId(id);

    db.ref('chloe_sessions/' + props.user.uid + '/' + id).update({
      updated_at: new Date().toISOString(),
      messages: messages
    }).catch(function(e) {
      console.warn('[chloe] session save failed:', e.message);
    });
  }, [messages]);

  function startNewSession() {
    setMessages([]);
    setAcceptedTools({});
    setSessionId('session-' + Date.now());
  }

  // Build API message history from local messages
  function buildApiMessages() {
    var apiMsgs = [];
    messages.forEach(function(msg) {
      if (msg.role === 'user') {
        if (msg.toolResults) {
          apiMsgs.push({ role: 'user', content: msg.toolResults });
        } else {
          apiMsgs.push({ role: 'user', content: msg.content });
        }
      } else if (msg.role === 'assistant') {
        var content = [];
        if (msg.content) content.push({ type: 'text', text: msg.content });
        if (msg.toolCalls) {
          msg.toolCalls.forEach(function(tc) {
            content.push({ type: 'tool_use', id: tc.id, name: tc.name, input: tc.input });
          });
        }
        if (content.length > 0) apiMsgs.push({ role: 'assistant', content: content });
      }
    });

    // Summarize if over 30 messages
    if (apiMsgs.length > 30) {
      var oldMessages = apiMsgs.slice(0, apiMsgs.length - 20);
      var recentMessages = apiMsgs.slice(apiMsgs.length - 20);

      var dropsCreated = 0;
      var slipsCreated = 0;
      var dropsMoved = 0;
      var rejections = [];

      oldMessages.forEach(function(msg) {
        if (msg.role === 'user' && Array.isArray(msg.content)) {
          msg.content.forEach(function(block) {
            if (block.type === 'tool_result' && block.content) {
              var text = block.content;
              var dropMatch = text.match(/(\d+) drops created/);
              if (dropMatch) dropsCreated += parseInt(dropMatch[1]);
              var slipMatch = text.match(/(\d+) slips created/);
              if (slipMatch) slipsCreated += parseInt(slipMatch[1]);
              var moveMatch = text.match(/(\d+) drops rescheduled/);
              if (moveMatch) dropsMoved += parseInt(moveMatch[1]);
              if (text.indexOf('Rejected') > -1) rejections.push(text);
            }
          });
        }
      });

      var summary = 'session so far:';
      if (dropsCreated > 0) summary += ' ' + dropsCreated + ' drops created.';
      if (slipsCreated > 0) summary += ' ' + slipsCreated + ' slips created.';
      if (dropsMoved > 0) summary += ' ' + dropsMoved + ' drops rescheduled.';
      if (rejections.length > 0) summary += ' ' + rejections.length + ' proposals rejected.';
      if (summary === 'session so far:') summary += ' general discussion, no actions taken.';

      apiMsgs = [{ role: 'user', content: '[context] ' + summary }].concat(recentMessages);
    }

    return apiMsgs;
  }

  function sendMessage(overrideUserMessage, toolResultContent) {
    var userText = overrideUserMessage || input.trim();
    if (!userText && !toolResultContent) return;

    // Add user message to display
    if (userText && !toolResultContent) {
      setMessages(function(prev) {
        return prev.concat([{ role: 'user', content: userText, timestamp: Date.now() }]);
      });
    }

    if (toolResultContent) {
      setMessages(function(prev) {
        return prev.concat([{ role: 'user', toolResults: toolResultContent, content: null, timestamp: Date.now(), hidden: true }]);
      });
    }

    setInput('');
    setLoading(true);

    var context = buildChloeContext(props);

    // We need to wait for state update, use timeout to ensure messages state is current
    setTimeout(function() {
      var apiMessages = buildApiMessages();
      if (userText && !toolResultContent) {
        apiMessages.push({ role: 'user', content: userText });
      }
      if (toolResultContent) {
        apiMessages.push({ role: 'user', content: toolResultContent });
      }

      getToken().then(function(token) {
        return aiFetch('/api/ai/chloe', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
          body: JSON.stringify({ messages: apiMessages, context: context })
        }, 45000);
      }).then(function(res) {
        return res.json();
      }).then(function(data) {
        setMessages(function(prev) {
          return prev.concat([{
            role: 'assistant',
            content: data.text || '',
            toolCalls: data.toolCalls && data.toolCalls.length > 0 ? data.toolCalls : null,
            timestamp: Date.now()
          }]);
        });
        setLoading(false);
      }).catch(function(e) {
        var errorMsg = formatAiError(e);
        setMessages(function(prev) {
          return prev.concat([{ role: 'assistant', content: errorMsg, isError: true, timestamp: Date.now() }]);
        });
        setLoading(false);
      });
    }, 50);
  }

  function markToolDecision(toolId, decision) {
    setAcceptedTools(function(prev) { var n = Object.assign({}, prev); n[toolId] = decision; return n; });
    // Persist decision into message data so it survives session reload
    setMessages(function(prev) {
      return prev.map(function(msg) {
        if (!msg.toolCalls) return msg;
        var updated = false;
        var newCalls = msg.toolCalls.map(function(tc) {
          if (tc.id === toolId) { updated = true; return Object.assign({}, tc, { decision: decision }); }
          return tc;
        });
        return updated ? Object.assign({}, msg, { toolCalls: newCalls }) : msg;
      });
    });
  }

  function handleAccept(toolCall) {
    var executor = CHLOE_EXECUTORS[toolCall.name];
    if (!executor) { showToast('NO EXECUTOR FOR ' + toolCall.name); return; }

    executor(toolCall.input, db, props).then(function(result) {
      markToolDecision(toolCall.id, true);
      showToast(String(result).toUpperCase());

      // Send tool_result back to Chloe
      var toolResultContent = [{
        type: 'tool_result',
        tool_use_id: toolCall.id,
        content: 'Accepted and executed. ' + result
      }];
      sendMessage(null, toolResultContent);
    }).catch(function(e) {
      showToast('FAILED: ' + e.message);
    });
  }

  function handleReject(toolCall) {
    markToolDecision(toolCall.id, 'rejected');

    var toolResultContent = [{
      type: 'tool_result',
      tool_use_id: toolCall.id,
      content: 'Rejected by user. Please adjust the proposal based on feedback.'
    }];
    sendMessage(null, toolResultContent);
  }

  function handleKeyDown(e) {
    if (e.key === 'Enter' && !e.shiftKey) {
      e.preventDefault();
      sendMessage();
    }
  }

  return (
    <div className="CH">
      <div style={{display:'flex',justifyContent:'space-between',padding:'8px 16px',borderBottom:'1px solid var(--border)'}}>
        <span style={{fontSize:10,color:'var(--text-dim)',letterSpacing:'.08em'}}>CHLOE</span>
        <button className="A-btn" style={{fontSize:9,padding:'2px 8px'}} onClick={startNewSession}>NEW SESSION</button>
      </div>
      <div className="CH-messages">
        {messages.length === 0 && (
          <div style={{display:'flex',flexDirection:'column',alignItems:'center',justifyContent:'center',height:'100%',color:'var(--text-dim)',gap:12}}>
            <div style={{fontFamily:'var(--display)',fontSize:'2rem',color:'var(--signal)',opacity:.3}}>C</div>
            <div style={{fontSize:10,textTransform:'uppercase',letterSpacing:'.1em'}}>chloe is ready</div>

            <ChloeContextNudges drops={props.drops} slips={props.slips} cards={props.cards} creators={props.creators} />

            <div style={{fontSize:10,color:'var(--text-dim)',maxWidth:'90%',textAlign:'center',lineHeight:1.6}}>
              try "fill next week with drops" or "any gaps in march?" or "build a scorecard 60 show"
            </div>
          </div>
        )}

        {messages.map(function(msg, idx) {
          if (msg.hidden) return null;

          if (msg.role === 'user') {
            return (
              <div key={idx} className="CH-msg CH-user">
                <div>{msg.content}</div>
              </div>
            );
          }

          return (
            <div key={idx} className="CH-msg CH-assistant">
              {msg.content && <div style={{whiteSpace:'pre-wrap',lineHeight:1.5,color:msg.isError?'var(--signal)':undefined}}>{msg.content}</div>}
              {msg.isError && (
                <button className="A-btn" style={{marginTop:6,fontSize:11,padding:'4px 12px'}} onClick={function() {
                  var lastUserMsg = null;
                  for (var ri = idx - 1; ri >= 0; ri--) { if (messages[ri].role === 'user' && messages[ri].content) { lastUserMsg = messages[ri].content; break; } }
                  if (lastUserMsg) {
                    setMessages(function(prev) { return prev.slice(0, idx); });
                    sendMessage(lastUserMsg);
                  }
                }}>RETRY</button>
              )}
              {msg.toolCalls && msg.toolCalls.map(function(tc) {
                return (
                  <ChloeActionCard
                    key={tc.id}
                    toolCall={tc}
                    onAccept={handleAccept}
                    onReject={handleReject}
                    accepted={acceptedTools[tc.id] || tc.decision}
                    brandSettings={props.brandSettings}
                    isMobile={isMobile}
                  />
                );
              })}
            </div>
          );
        })}

        {loading && (
          <div className="CH-msg CH-assistant">
            <div className="CH-typing">
              <span></span><span></span><span></span>
            </div>
          </div>
        )}

        <div ref={messagesEndRef}></div>
      </div>

      <div className="CH-input-bar">
        <textarea
          ref={inputRef}
          className="A-textarea"
          value={input}
          onChange={function(e) { setInput(e.target.value); }}
          onKeyDown={handleKeyDown}
          placeholder="talk to chloe..."
          rows="1"
          style={{resize:'none',minHeight:'unset',flex:1,border:'none',background:'transparent',padding:'10px 0'}}
        />
        <button
          className="A-btn signal"
          onClick={function() { sendMessage(); }}
          disabled={loading || !input.trim()}
          style={{padding:'8px 16px',flexShrink:0}}
        >
          {loading ? '...' : 'SEND'}
        </button>
      </div>
    </div>
  );
}
