// Sidebar + Topbar

const cronToHuman = (expr) => {
  if (!expr || typeof expr !== 'string') return '';
  const p = expr.trim().split(/\s+/);
  if (p.length !== 5) return expr;
  const [min, hour, , , dow] = p;
  const t = (h, m) => m === '0' || m === '00'
    ? `${h}h` : `${h}:${m.padStart(2,'0')}`;
  if (min.startsWith('*/')) return `a cada ${min.slice(2)} min`;
  if (hour.startsWith('*/') && min === '0') return `a cada ${hour.slice(2)}h`;
  const days = ['domingo','segunda','terça','quarta','quinta','sexta','sábado'];
  if (dow !== '*' && /^\d$/.test(dow)) return `toda ${days[+dow]} às ${t(hour,min)}`;
  if (dow === '1-5') return `dias úteis às ${t(hour,min)}`;
  return `todo dia às ${t(hour,min)}`;
};

const initials = (name) =>
  name.split(/\s+/).slice(0, 2).map(w => w[0]).join("").toUpperCase();

const modelFamily = (model) => {
  const m = (model || '').toLowerCase();
  if (!m || m === 'none') return 'none';
  if (m.includes('haiku')) return 'haiku';
  if (m.includes('sonnet')) return 'sonnet';
  if (m.includes('opus')) return 'opus';
  return 'other';
};

const modelLabel = (model) => {
  const f = modelFamily(model);
  if (f === 'none') return 'none';
  if (f === 'other') return model || 'other';
  return f;
};

const ModelBadge = ({ model, compact = false }) => {
  const family = modelFamily(model);
  if (family === 'none') return null;
  return (
    <span
      className={"model-badge model-" + family + (compact ? " compact" : "")}
      title={model || family}
    >
      {modelLabel(model)}
    </span>
  );
};

// Helper: relative time from ISO or already-relative string
const relativeTime = (val) => {
  if (!val) return null;
  // Already a relative string (e.g., "há 2 min")
  if (typeof val === 'string' && !/^\d{4}-/.test(val)) return val;
  try {
    const diff = Date.now() - new Date(val).getTime();
    if (diff < 0) return 'agora';
    if (diff < 60000) return 'agora';
    if (diff < 3600000) return 'há ' + Math.floor(diff / 60000) + 'm';
    if (diff < 86400000) return 'há ' + Math.floor(diff / 3600000) + 'h';
    return 'há ' + Math.floor(diff / 86400000) + 'd';
  } catch {
    return val;
  }
};

const computeHealthDots = (agent) => {
  // Heurística de fallback enquanto backend não expõe agent.recent_runs:
  // - dot 1: derivado de last_exit_code (0 -> ok, !=0 -> fail, null -> unknown)
  // - dots 2 e 3: unknown (placeholder cinza)
  // Se backend já expõe agent.recent_runs (array de 0-3 exit codes), usar.
  if (Array.isArray(agent.recent_runs) && agent.recent_runs.length > 0) {
    const slice = agent.recent_runs.slice(0, 3);
    while (slice.length < 3) slice.push(null);
    return slice.map(ec => ec == null ? "unknown" : (ec === 0 ? "ok" : "fail"));
  }
  const first =
    agent.last_exit_code == null ? "unknown" :
    agent.last_exit_code === 0 ? "ok" : "fail";
  const second = (agent.last_runs_failed > 0 && agent.last_exit_code === 0) ? "fail" : "unknown";
  return [first, second, "unknown"];
};

const AgentRow = ({ agent, agentCrons, active, onClick, density, draggable }) => {
  const isMac = agent.origin === 'mac' || agent.origin === 'mac-launchd' || agent.origin === 'mac-hook';
  const originLabel = isMac ? 'mac' : (agent.origin === 'vps' ? 'vps' : (agent.origin || null));
  const originTitle = isMac ? 'Mac pode estar dormindo' : (agent.origin === 'vps' ? 'VPS — sempre online' : (agent.origin || ''));

  // Cadence: pick the most-frequent enabled cron for this agent
  const enabledCrons = (agentCrons || []).filter(c => c.enabled);
  const cadenceText = enabledCrons.length > 0
    ? (enabledCrons[0].natural || enabledCrons[0].expr)
    : 'on-demand';
  const hasCron = enabledCrons.length > 0 || (agent.cadence && agent.cadence.length > 0);

  const lastRunText = relativeTime(agent.lastRun);
  const healthDots = computeHealthDots(agent);

  // Derived "scheduled" status — for cron-only agents that don't heartbeat
  // (launchd jobs, etc.). API returns status=offline between runs, which is
  // misleading: the job is scheduled and ran recently. Promote to a green
  // "Agendado" pill when there's a cadence AND last_run_at within the last
  // 26 hours (covers daily/sub-daily cadences with a small grace window).
  let pillStatus = agent.status;
  let pillLabel = agent.statusLabel;
  if (agent.status === 'offline' && hasCron && agent.last_run_at) {
    const lastRunMs = Date.parse(agent.last_run_at);
    if (!isNaN(lastRunMs) && (Date.now() - lastRunMs) < 26 * 60 * 60 * 1000) {
      pillStatus = 'scheduled';
      pillLabel = 'Agendado';
    }
  }

  return (
    <div
      className={"agent-row" + (active ? " active" : "") + (draggable ? " agent-row-draggable" : "")}
      onClick={onClick}
      style={{ position: 'relative' }}
      data-agent-id={agent.id}
    >
      {agent.photo ? (
        <img
          className="avatar avatar-photo"
          src={agent.photo}
          alt={agent.name}
          onError={(e) => {
            const node = e.currentTarget;
            const fallback = document.createElement('div');
            fallback.className = 'avatar';
            fallback.style.background = agent.avatarColor || '';
            fallback.textContent = initials(agent.name);
            node.replaceWith(fallback);
          }}
        />
      ) : (
        <div className="avatar" style={{ background: agent.avatarColor }}>
          {initials(agent.name)}
        </div>
      )}
      <div className="agent-meta">
        <div className="agent-name">{agent.name}</div>
        {density !== "compact" && (
          <div className="agent-role">
            {agent.role}
          </div>
        )}
        {density !== "compact" && (
          <div className="agent-row-signals">
            <span className="cadence-chip">
              <Icon name={hasCron ? "clock_simple" : "hand"} size={10} style={{ verticalAlign: -1 }} />
              {' '}{cadenceText}
            </span>
            {lastRunText && (
              <span className="cadence-chip" style={{ color: 'var(--d-fg-4)' }}>
                {lastRunText}
              </span>
            )}
            <span className="health-dots" title="Saúde das últimas execuções">
              {healthDots.map((d, i) => (
                <span key={i} className={"health-dot " + d}></span>
              ))}
            </span>
          </div>
        )}
      </div>
      <ModelBadge model={agent.model} compact />
      {originLabel && (
        <span className={"origin-badge origin-" + originLabel} title={originTitle}>
          {originLabel}
        </span>
      )}
      <span className={"pill " + pillStatus}>{pillLabel}</span>
      {agent.pendingApprovals > 0 && (
        <div className="agent-row-pending">{agent.pendingApprovals}</div>
      )}
    </div>
  );
};

const STATUS_FILTERS = [
  { key: "all", label: "Todos" },
  { key: "idle", label: "Disponível" },
  { key: "running", label: "Executando" },
  { key: "offline", label: "Offline" },
  { key: "error", label: "Erro" },
];

const MODEL_FILTERS = [
  { key: "all", label: "Modelos" },
  { key: "haiku", label: "Haiku" },
  { key: "sonnet", label: "Sonnet" },
  { key: "opus", label: "Opus" },
  { key: "none", label: "None" },
];

const Sidebar = ({ agents, crons, activeId, onSelect, density, onReorder }) => {
  const [q, setQ] = React.useState("");
  const [statusFilter, setStatusFilter] = React.useState("all");
  const [modelFilter, setModelFilter] = React.useState("all");

  const inputRef = React.useRef(null);
  const isMac = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform);
  const shortcutHint = isMac ? "⌘K" : "Ctrl+K";

  React.useEffect(() => {
    const handler = (e) => {
      const cmdOrCtrl = isMac ? e.metaKey : e.ctrlKey;
      if (cmdOrCtrl && (e.key === "k" || e.key === "K")) {
        e.preventDefault();
        if (inputRef.current) inputRef.current.focus();
      }
    };
    window.addEventListener("keydown", handler);
    return () => window.removeEventListener("keydown", handler);
  }, [isMac]);

  // Build crons-by-agent map
  const cronsByAgent = React.useMemo(() => {
    const map = {};
    if (!Array.isArray(crons)) return map;
    crons.forEach(c => {
      const aid = c.agentId || c.agent_id;
      if (!aid) return;
      if (!map[aid]) map[aid] = [];
      map[aid].push(c);
    });
    return map;
  }, [crons]);

  const filtersActive = q !== "" || statusFilter !== "all" || modelFilter !== "all";

  const filtered = React.useMemo(() => {
    const needle = q.trim().toLowerCase();
    let list = agents;
    if (needle) {
      list = list.filter(a => {
        const haystack = [
          a.name || "",
          a.role || "",
          a.model || "",
          a.description || "",
          ...(Array.isArray(a.traits) ? a.traits : []),
          ...(Array.isArray(a.integrations) ? a.integrations : []),
          ...(Array.isArray(a.capabilities) ? a.capabilities : []),
        ].join(" ").toLowerCase();
        return haystack.includes(needle);
      });
    }
    if (statusFilter !== "all") list = list.filter(a => a.status === statusFilter);
    if (modelFilter !== "all") list = list.filter(a => modelFamily(a.model) === modelFilter);
    // Order respects server-side display_order (set by drag-and-drop reorder).
    // Errors/pending no longer "float to top" — explicit user ordering wins;
    // use the status filter chip to surface errors when needed.
    return list;
  }, [agents, crons, q, statusFilter, modelFilter]);

  // Drag-and-drop reorder via SortableJS. Disabled while any filter is active
  // (reordering a filtered subset would silently shuffle the global order).
  const listRef = React.useRef(null);
  React.useEffect(() => {
    if (!listRef.current || typeof window === 'undefined' || !window.Sortable) return;
    if (!onReorder) return;
    const sortable = window.Sortable.create(listRef.current, {
      animation: 150,
      disabled: filtersActive,
      handle: '.agent-row',          // whole card is the handle
      ghostClass: 'agent-row-ghost', // styled in dashboard.css
      chosenClass: 'agent-row-chosen',
      dragClass: 'agent-row-drag',
      onEnd: () => {
        const ids = Array.from(listRef.current.children)
          .map(el => el.getAttribute && el.getAttribute('data-agent-id'))
          .filter(Boolean);
        if (ids.length > 0) onReorder(ids);
      },
    });
    return () => { try { sortable.destroy(); } catch {} };
  }, [filtersActive, onReorder, filtered.length]);

  return (
    <aside className="sidebar">
      <div className="sidebar-head">
        <div className="sidebar-title">
          <h2>Equipe</h2>
          <span className="count">{filtered.length} {filtered.length === 1 ? 'agente' : 'agentes'}</span>
        </div>
        <div className="search">
          <Icon name="search" size={14} />
          <input
            ref={inputRef}
            placeholder={`buscar nome, traço, integração... (${shortcutHint})`}
            value={q}
            onChange={(e) => setQ(e.target.value)}
          />
        </div>
        <div className="status-filters">
          {STATUS_FILTERS.map(f => (
            <button
              key={f.key}
              className={"filter-chip" + (statusFilter === f.key ? " active" : "")}
              onClick={() => setStatusFilter(f.key)}
            >{f.label}</button>
          ))}
        </div>
        <div className="status-filters model-filters">
          {MODEL_FILTERS.map(f => (
            <button
              key={f.key}
              className={"filter-chip model-filter-" + f.key + (modelFilter === f.key ? " active" : "")}
              onClick={() => setModelFilter(f.key)}
            >{f.label}</button>
          ))}
        </div>
      </div>
      <div className="agent-list" ref={listRef}>
        {filtered.map(a => (
          <AgentRow
            key={a.id}
            agent={a}
            agentCrons={cronsByAgent[a.id] || []}
            active={a.id === activeId}
            onClick={() => onSelect(a.id)}
            density={density}
            draggable={!!onReorder && !filtersActive}
          />
        ))}
        {filtered.length === 0 && (
          <div style={{padding: "20px 16px", color: "var(--d-fg-3)", fontSize: 13, textAlign: "center", lineHeight: 1.6}}>
            Nenhum agente encontrado.
            {q && <div style={{marginTop:6, fontSize:12}}>Tente limpar a busca.</div>}
            {!q && statusFilter !== 'all' && (
              <div style={{marginTop:6, fontSize:12}}>
                Nenhum agente com este status agora.{' '}
                <span style={{color:"var(--d-brand)", cursor:"pointer"}} onClick={() => setStatusFilter('all')}>
                  Ver todos →
                </span>
              </div>
            )}
            {!q && statusFilter === 'all' && modelFilter !== 'all' && (
              <div style={{marginTop:6, fontSize:12}}>
                Nenhum agente com este modelo.{' '}
                <span style={{color:"var(--d-brand)", cursor:"pointer"}} onClick={() => setModelFilter('all')}>
                  Ver todos →
                </span>
              </div>
            )}
          </div>
        )}
      </div>
    </aside>
  );
};

const BudgetGauge = () => {
  const [data, setData] = React.useState(null);

  React.useEffect(() => {
    const fetchBudget = () => {
      const tok = localStorage.getItem('hub_token');
      if (!tok) return;
      fetch('/api/v1/budget', { headers: { Authorization: 'Bearer ' + tok } })
        .then(r => r.ok ? r.json() : null)
        .then(d => { if (d) setData(d); })
        .catch(() => {});
    };
    fetchBudget();
    const id = setInterval(fetchBudget, 60000);
    return () => clearInterval(id);
  }, []);

  if (!data || !data.cap_cents || data.cap_cents <= 0) return null;
  const cost = data.cost_30d_cents || 0;
  const cap = data.cap_cents;
  const pct = Math.min(100, Math.round((cost / cap) * 100));
  const tone = pct >= 90 ? 'red' : pct >= 60 ? 'yellow' : 'green';
  const fmt = (c) => '$' + (c / 100).toFixed(2);

  return (
    <div className={'budget-gauge tone-' + tone} title={'Custo hub · 30 dias rolantes'}>
      <span className="budget-label">{fmt(cost)} / {fmt(cap)} ({pct}%)</span>
      <div className="budget-bar"><div className="budget-fill" style={{ width: pct + '%' }} /></div>
    </div>
  );
};
window.BudgetGauge = BudgetGauge;

const NotificationsPanel = ({ onClose }) => (
  <div className="notif-panel" onClick={(e) => e.stopPropagation()}>
    <div className="notif-header">
      <span>Notificações</span>
      <button className="icon-btn" onClick={onClose}><Icon name="x" size={14}/></button>
    </div>
    <div className="notif-empty">
      <Icon name="bell" size={28} />
      <p>Nenhuma notificação</p>
      <span>Falhas, alertas de custo e aprovações pendentes aparecerão aqui.</span>
    </div>
  </div>
);

const Topbar = ({ pendingCount, view, onSetView }) => {
  const [showNotif, setShowNotif] = React.useState(false);

  const openSettings = () => {
    window.postMessage({ type: '__activate_edit_mode' }, '*');
  };

  const handleLogout = () => {
    localStorage.removeItem('hub_token');
    window.location = '/login';
  };

  return (
    <header className="topbar" onClick={() => setShowNotif(false)}>
      <div className="brand">
        <div className="brand-mark">v</div>
        <span>vini-agents</span>
      </div>
      <span className="tag brand">Command Center · v0.3</span>
      <span className="tag" title="Status do hub — não dos agentes individuais"><span className="dot"></span>hub online · 0 falhas</span>
      <BudgetGauge />

      {/* D-07: view toggle */}
      {onSetView && (
        <div className="view-toggle">
          <button
            className={"view-toggle-btn" + (view === 'org' ? " active" : "")}
            onClick={() => onSetView('org')}
            title="Visão organograma"
          >
            Organograma
          </button>
          <button
            className={"view-toggle-btn" + (view === 'detail' ? " active" : "")}
            onClick={() => onSetView('detail')}
            title="Visão detalhe"
          >
            Detalhe
          </button>
        </div>
      )}

      <div className="spacer"></div>
      {pendingCount > 0 ? (
        <span className="tag warn" style={{cursor:"pointer"}}>{pendingCount} aprovações pendentes</span>
      ) : (
        <span className="tag" style={{opacity: 0.5}}>0 aprovações pendentes</span>
      )}
      <div style={{position:"relative"}}>
        <button
          className={"icon-btn" + (showNotif ? " active" : "")}
          title="Notificações"
          onClick={(e) => { e.stopPropagation(); setShowNotif(v => !v); }}
        >
          <Icon name="bell" size={17} />
        </button>
        {showNotif && <NotificationsPanel onClose={() => setShowNotif(false)} />}
      </div>
      <button className="icon-btn" title="Configurações" onClick={openSettings}>
        <Icon name="settings" size={17} />
      </button>
      <button className="icon-btn" title="Sair" onClick={handleLogout} style={{color:"var(--d-fg-3)"}}>
        <Icon name="logout" size={17} />
      </button>
    </header>
  );
};

window.Sidebar = Sidebar;
window.Topbar = Topbar;
window.cronToHuman = cronToHuman;
window.initials = initials;
window.modelFamily = modelFamily;
window.modelLabel = modelLabel;
window.ModelBadge = ModelBadge;
