// Main App

const useEscapeToClose = (active, onClose) => {
  React.useEffect(() => {
    if (!active) return;
    const handler = (e) => { if (e.key === "Escape") onClose(); };
    window.addEventListener("keydown", handler);
    return () => window.removeEventListener("keydown", handler);
  }, [active, onClose]);
};
window.useEscapeToClose = useEscapeToClose;

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "density": "cozy",
  "accent": "blue",
  "showStats": true
}/*EDITMODE-END*/;

const ACCENT_COLORS = {
  blue: "#7B9DEC",
  green: "#7BC773",
  purple: "#B589E0",
  amber: "#F0B45C",
};

const AGENT_DEFAULTS = {
  avatarColor: '#7B9DEC',
  description: '',
  personality: '',
  quote: '',
  lastRun: '',
  runsToday: 0,
  successRate: 0,
  pendingApprovals: 0,
};

const authHeader = (token) => ({ Authorization: `Bearer ${token}` });

const enrichCron = (cron) => ({
  ...cron,
  agentId: cron.agent_id,
  natural: typeof window.cronToHuman === 'function' ? window.cronToHuman(cron.expr) : cron.expr,
  next: '—',
  countdown: '—',
});

const EmptyDashboardState = () => (
  <div className="dashboard-empty-card main-empty-card">
    <div className="dashboard-empty-kicker">API conectada · sem agentes</div>
    <h2>Registrar primeiro agente</h2>
    <p>
      O frontend não usa mais dados fictícios. Quando algum app chamar
      <code> register_agent()</code> ou o mirror publicar jobs, eles aparecerão aqui.
    </p>
    <div className="dashboard-empty-actions">
      <code>AGENT_HUB_URL</code>
      <code>AGENT_HUB_API_KEY</code>
      <code>AGENT_ID</code>
    </div>
  </div>
);

const App = () => {
  const [agents, setAgents] = React.useState([]);
  const [loading, setLoading] = React.useState(true);
  const [authError, setAuthError] = React.useState(null);
  const [crons, setCrons] = React.useState([]);
  const [activeId, setActiveId] = React.useState(null);
  const [tweaks, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const [toast, setToast] = React.useState(null);
  const [runningAgentIds, setRunningAgentIds] = React.useState(() => new Set());

  const [confirmRunAgentId, setConfirmRunAgentId] = React.useState(null);
  useEscapeToClose(!!confirmRunAgentId, () => setConfirmRunAgentId(null));

  // D-07: view toggle — 'org' | 'detail', persisted in localStorage
  const [view, setViewRaw] = React.useState(() => {
    try { return localStorage.getItem('hub_view') || 'detail'; } catch { return 'detail'; }
  });
  const setView = (v) => {
    setViewRaw(v);
    try { localStorage.setItem('hub_view', v); } catch {}
  };

  // Drawer state for org-chart node click
  const [drawerAgentId, setDrawerAgentId] = React.useState(null);

  // D-06: chat scope
  const [chatScope, setChatScope] = React.useState({ type: 'all', agentId: null });

  // Executions drill-down drawer
  const [executionsDrawerAgentId, setExecutionsDrawerAgentId] = React.useState(null);
  const [orgChartView, setOrgChartView] = React.useState(() => window.OrgChart || null);

  const onToast = React.useCallback((msg) => {
    setToast(msg);
  }, []);

  const handleRunAgent = React.useCallback(async (agent) => {
    const tok = localStorage.getItem('hub_token');
    if (!tok) {
      throw new Error('sessão expirada');
    }

    const body = {
      message: `Disparo manual pelo hub para ${agent.name}.`,
    };
    if (agent.id === 'my-daily') {
      body.push = false;
    }
    if (agent.id === 'slide-maker') {
      body.prompt = agent.description || agent.role || 'Disparo manual pelo hub.';
    }

    setRunningAgentIds((prev) => {
      const next = new Set(prev);
      next.add(agent.id);
      return next;
    });

    try {
      const response = await fetch(`/api/v1/agents/${agent.id}/run`, {
        method: 'POST',
        headers: { ...authHeader(tok), 'Content-Type': 'application/json' },
        body: JSON.stringify(body),
      });

      const data = await response.json().catch(() => ({}));
      if (!response.ok) {
        throw new Error(data.detail || data.reply || `Erro ${response.status}`);
      }
      return data;
    } finally {
      setRunningAgentIds((prev) => {
        const next = new Set(prev);
        next.delete(agent.id);
        return next;
      });
    }
  }, []);

  const handleRequestRun = React.useCallback((agent) => {
    if (!agent) return;
    setConfirmRunAgentId(agent.id);
  }, []);

  const handleConfirmRun = React.useCallback(async () => {
    const ag = agents.find(a => a.id === confirmRunAgentId);
    if (!ag) { setConfirmRunAgentId(null); return; }
    // Close modal immediately — runningAgentIds Set drives the "Executando..." pill on AgentHero.
    setConfirmRunAgentId(null);
    onToast(`Disparando ${ag.name}…`);
    try {
      const result = await handleRunAgent(ag);
      const reply = result?.reply || result?.response || result?.status || 'execução concluída';
      onToast(`${ag.name}: ${String(reply).slice(0, 140)}`);
    } catch (err) {
      onToast(`${ag.name} — falha: ${err?.message || 'erro ao executar'}`);
    }
  }, [agents, confirmRunAgentId, handleRunAgent, onToast]);

  const loadAgents = React.useCallback((token) => {
    return fetch('/api/v1/agents', { headers: authHeader(token) })
      .then((r) => {
        if (r.status === 401) {
          localStorage.removeItem('hub_token');
          window.location = '/login';
          return null;
        }
        if (!r.ok) {
          setAuthError(`Erro ${r.status}`);
          return null;
        }
        return r.json();
      })
      .then((data) => {
        if (!Array.isArray(data)) return;
        const withDefaults = data.map(a => ({
          ...AGENT_DEFAULTS,
          capabilities: a.integrations || [],
          traits: a.traits || [],
          ...a,
        }));
        setAgents(withDefaults);
      })
      .catch(() => setAuthError('Erro de rede'));
  }, []);

  const handleReorder = React.useCallback((orderedIds) => {
    const tok = localStorage.getItem('hub_token');
    if (!tok || !Array.isArray(orderedIds) || orderedIds.length === 0) return;
    // Optimistic: reorder local state so the UI doesn't flicker back.
    setAgents((prev) => {
      const byId = {};
      prev.forEach(a => { byId[a.id] = a; });
      const reordered = orderedIds.map(id => byId[id]).filter(Boolean);
      const rest = prev.filter(a => !orderedIds.includes(a.id));
      return [...reordered, ...rest];
    });
    fetch('/api/v1/agents/reorder', {
      method: 'POST',
      headers: { ...authHeader(tok), 'Content-Type': 'application/json' },
      body: JSON.stringify({ order: orderedIds }),
    }).then((r) => {
      if (!r.ok) {
        // Server rejected — re-fetch to resync (rollback optimistic update).
        loadAgents(tok);
      }
    }).catch(() => loadAgents(tok));
  }, []);

  const loadCrons = React.useCallback((token) => {
    return fetch('/api/v1/crons', { headers: authHeader(token) })
      .then(r => r.ok ? r.json() : [])
      .then(data => { if (Array.isArray(data)) setCrons(data.map(enrichCron)); })
      .catch(() => {});
  }, []);

  const handleToggleCron = React.useCallback((cronId) => {
    const tok = localStorage.getItem('hub_token');
    const cron = crons.find(c => c.id === cronId);
    if (!cron) return;
    const newEnabled = !cron.enabled;
    setCrons(prev => prev.map(c => c.id === cronId ? { ...c, enabled: newEnabled } : c));
    fetch(`/api/v1/crons/${cronId}`, {
      method: 'PATCH',
      headers: { ...authHeader(tok), 'Content-Type': 'application/json' },
      body: JSON.stringify({ enabled: newEnabled }),
    }).catch(() => loadCrons(tok));
  }, [crons, loadCrons]);

  const handleDeleteCron = React.useCallback((cronId) => {
    const tok = localStorage.getItem('hub_token');
    setCrons(prev => prev.filter(c => c.id !== cronId));
    fetch(`/api/v1/crons/${cronId}`, {
      method: 'DELETE',
      headers: authHeader(tok),
    }).catch(() => loadCrons(tok));
  }, [loadCrons]);

  const handleAddCron = React.useCallback((text, expr, agentId) => {
    const tok = localStorage.getItem('hub_token');
    fetch('/api/v1/crons', {
      method: 'POST',
      headers: { ...authHeader(tok), 'Content-Type': 'application/json' },
      body: JSON.stringify({
        agent_id: agentId,
        title: text.split('\n')[0].slice(0, 60),
        description: text,
        expr: expr || '0 9 * * *',
      }),
    })
      .then(r => r.ok ? r.json() : null)
      .then(cron => { if (cron) setCrons(prev => [...prev, enrichCron(cron)]); })
      .catch(() => {});
  }, []);

  React.useEffect(() => {
    const token = localStorage.getItem('hub_token');
    if (!token) { window.location = '/login'; return; }
    Promise.all([loadAgents(token), loadCrons(token)]).then(() => setLoading(false));

    // auto-refresh agents a cada 30s
    const interval = setInterval(() => loadAgents(token), 30000);
    return () => clearInterval(interval);
  }, [loadAgents, loadCrons]);

  React.useEffect(() => {
    if (window.OrgChart) {
      setOrgChartView(() => window.OrgChart);
      return;
    }
    const interval = setInterval(() => {
      if (!window.OrgChart) return;
      setOrgChartView(() => window.OrgChart);
      clearInterval(interval);
    }, 50);
    const timeout = setTimeout(() => clearInterval(interval), 5000);
    return () => {
      clearInterval(interval);
      clearTimeout(timeout);
    };
  }, []);

  React.useEffect(() => {
    if (agents.length > 0 && activeId === null) setActiveId(agents[0].id);
  }, [agents, activeId]);

  React.useEffect(() => {
    document.body.dataset.density = tweaks.density;
    document.documentElement.style.setProperty("--d-brand", ACCENT_COLORS[tweaks.accent] || ACCENT_COLORS.blue);
    const c = ACCENT_COLORS[tweaks.accent] || ACCENT_COLORS.blue;
    const r = parseInt(c.slice(1,3),16), g = parseInt(c.slice(3,5),16), b = parseInt(c.slice(5,7),16);
    document.documentElement.style.setProperty("--d-brand-soft", `rgba(${r},${g},${b},0.14)`);
  }, [tweaks.density, tweaks.accent]);

  const active = agents.find(a => a.id === activeId);
  const pendingCount = agents.reduce((s, a) => s + (a.pendingApprovals || 0), 0);

  // Agent lookup map for drawer
  const agentById = React.useMemo(() => {
    const m = {};
    agents.forEach(a => { m[a.id] = a; });
    return m;
  }, [agents]);

  const drawerAgent = drawerAgentId ? agentById[drawerAgentId] : null;

  if (loading) return <div className="app-loading">Carregando agentes...</div>;
  if (authError) return <div className="app-loading">{authError}</div>;

  return (
    <div className={"app" + (view === 'org' ? ' view-org' : ' view-detail')}>
      <Topbar
        pendingCount={pendingCount}
        view={view}
        onSetView={setView}
      />

      {/* Sidebar only in detail view */}
      {view === 'detail' && (
        <Sidebar
          agents={agents}
          crons={crons}
          activeId={activeId}
          onSelect={setActiveId}
          density={tweaks.density}
          onReorder={handleReorder}
        />
      )}

      <main className={"main" + (view === 'org' ? ' main-org' : '')}>
        {view === 'org' ? (
          orgChartView ? (
            React.createElement(orgChartView, {
              agents,
              crons,
              onRunAgent: handleRunAgent,
              onSelect: (id) => {
                setDrawerAgentId(id);
                setChatScope({ type: 'agent', agentId: id });
              },
              setChatScope,
            })
          ) : (
            <div className="orgchart-wrap orgchart-empty-wrap">
              <div className="dashboard-empty-card">
                <div className="dashboard-empty-kicker">Carregando visualização</div>
                <h2>Preparando organograma</h2>
                <p>O dashboard está aguardando o componente <code>orgchart.jsx</code>.</p>
              </div>
            </div>
          )
        ) : (
          <div className="main-inner">
            {active ? (
              <>
                <AgentHero
                  key={active.id}
                  agent={active}
                  onRunAgent={handleRunAgent}
                  onRequestRun={handleRequestRun}
                  onToast={onToast}
                  isRunning={runningAgentIds.has(active.id)}
                />
                {tweaks.showStats && (
                  <StatStrip
                    agent={active}
                    onStatClick={() => setExecutionsDrawerAgentId(active.id)}
                  />
                )}
                <div className="section">
                  <ProfileCards agent={active} />
                </div>
                <CronSection
                  agent={active}
                  crons={crons}
                  onToggle={handleToggleCron}
                  onDelete={handleDeleteCron}
                  onAdd={(text, expr) => handleAddCron(text, expr, active.id)}
                />
              </>
            ) : (
              <EmptyDashboardState />
            )}
          </div>
        )}

        {/* Chat always visible below */}
        <div className={view === 'org' ? 'org-chat-wrap' : 'main-inner'} style={view === 'org' ? {} : { marginTop: 0, paddingTop: 0 }}>
          <ChatPanel agents={agents} scope={chatScope} setScope={setChatScope} activeId={activeId} />
        </div>
      </main>

      {/* Agent drawer (from org-chart node click) */}
      {drawerAgent && (
        <AgentDrawer
          key={drawerAgent.id}
          agent={drawerAgent}
          crons={crons}
          onClose={() => setDrawerAgentId(null)}
          onToast={onToast}
          onStatClick={(agentId) => setExecutionsDrawerAgentId(agentId)}
          onRunAgent={handleRunAgent}
          onToggleCron={handleToggleCron}
          onDeleteCron={handleDeleteCron}
          onAddCron={handleAddCron}
          isRunning={runningAgentIds.has(drawerAgent.id)}
        />
      )}

      {/* Executions drill-down drawer */}
      {executionsDrawerAgentId && (
        <ExecutionsDrawer
          agentId={executionsDrawerAgentId}
          onClose={() => setExecutionsDrawerAgentId(null)}
        />
      )}

      <TweaksPanel>
        <TweakSection label="Densidade" />
        <TweakRadio
          label="Tamanho"
          value={tweaks.density}
          options={["compact", "cozy"]}
          onChange={(v) => setTweak("density", v)}
        />
        <TweakSection label="Cor de destaque" />
        <TweakRadio
          label="Accent"
          value={tweaks.accent}
          options={["blue", "green", "purple", "amber"]}
          onChange={(v) => setTweak("accent", v)}
        />
        <TweakSection label="Visualização" />
        <TweakToggle
          label="Mostrar métricas"
          value={tweaks.showStats}
          onChange={(v) => setTweak("showStats", v)}
        />
      </TweaksPanel>

      {toast && <Toast msg={toast} onDone={() => setToast(null)} />}
      {confirmRunAgentId && (() => {
        const Modal = window.ConfirmRunModal;
        const ag = agents.find(a => a.id === confirmRunAgentId);
        if (!Modal || !ag) return null;
        return (
          <Modal
            agent={ag}
            running={runningAgentIds.has(ag.id)}
            onCancel={() => setConfirmRunAgentId(null)}
            onConfirm={handleConfirmRun}
          />
        );
      })()}
    </div>
  );
};

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