/**
 * SessionTree — flat list rendering of main sessions and sub-sessions.
 *
 * Main sessions render at indent 0. Sub-sessions render at indent 1
 * (paddingLeft). Each node shows: agent type badge, label/name, and a state
 * indicator dot (running = green, idle = dim, stopped = red).
 *
 * Transport sessions (runtimeType === 'transport') show a cloud icon instead
 * of the default terminal icon.
 *
 * Task 2.4: Unread badge shown next to session name when count > 0.
 * Task 2.5: Idle flash applied for new realtime session.idle events.
 * Task 2.6: Main click → onSelectSession; sub-session click → onSelectSubSession.
 */

import { useEffect, useMemo, useState } from 'preact/hooks';
import { memo } from 'preact/compat';
import { useTranslation } from 'react-i18next';
import type { SessionInfo } from '../types.js';
import { isTransportRuntime } from '../runtime-type.js';
import type { SubSession } from '../hooks/useSubSessions.js';
import { formatLabel } from '../format-label.js';
import { getAgentBadgeConfig } from '../agent-display.js';
import { IdleFlashLayer } from './IdleFlashLayer.js';
import { useIdleFlashPlayback } from '../hooks/useIdleFlashPlayback.js';

interface Props {
  serverId?: string | null;
  sessions: SessionInfo[];
  subSessions: SubSession[];
  activeSession: string | null;
  /** Map<sessionName, unreadCount> — supplied by useUnreadCounts */
  unreadCounts: Map<string, number>;
  /** Per-session idle flash replay token. */
  idleFlashTokens?: Map<string, number>;
  /** Set of sub-session labels participating in active P2P discussions. */
  p2pSessionLabels?: Set<string>;
  onSelectSession: (sessionName: string) => void;
  onSelectSubSession: (sub: SubSession) => void;
  /** Open new session dialog. */
  onNewSession?: () => void;
  /** Open new sub-session dialog. */
  onNewSubSession?: () => void;
}

// ── Helper: compute label for a main session ─────────────────────────────────
function getSessionLabel(s: SessionInfo): string {
  if (s.label) return formatLabel(s.label);
  return s.role === 'brain' ? s.project : `W${s.name.split('_w')[1] ?? '?'}`;
}

// ── State dot ────────────────────────────────────────────────────────────────
function StateDot({ state }: { state: string }) {
  let color: string;
  if (state === 'running' || state === 'queued') color = '#4ade80';
  else if (state === 'idle') color = '#64748b';
  else if (state === 'stopping') color = '#f59e0b';
  else if (state === 'stopped' || state === 'error') color = '#ef4444';
  else color = '#64748b';
  return (
    <span
      class="session-tree-state-dot"
      style={{ background: color }}
      title={state}
    />
  );
}

// ── Unread badge ──────────────────────────────────────────────────────────────
function UnreadBadge({ count }: { count: number }) {
  if (count <= 0) return null;
  return (
    <span class="sidebar-unread-badge" aria-label={String(count)}>
      {count > 99 ? '99+' : count}
    </span>
  );
}

// ── Single session node (main or sub-session) ─────────────────────────────────
interface NodeProps {
  label: string;
  agentType: string;
  state: string;
  isActive: boolean;
  isTransport?: boolean;
  isSub?: boolean;
  unread: number;
  idleFlashToken: number;
  inP2p?: boolean;
  onClick: () => void;
}

function SessionNode({
  label, agentType, state, isActive, isTransport, isSub, unread, idleFlashToken, inP2p, onClick,
}: NodeProps) {
  const { t } = useTranslation();
  const activeIdleFlashToken = useIdleFlashPlayback(idleFlashToken);
  const badge = getAgentBadgeConfig(agentType);

  const classes = [
    'session-tree-node',
    isSub ? 'session-tree-node--sub' : 'session-tree-node--main',
    isActive ? 'session-tree-node--active' : '',
  ].filter(Boolean).join(' ');

  return (
    <button class={classes} onClick={onClick} title={`${agentType} — ${state}`}>
      {activeIdleFlashToken ? <IdleFlashLayer key={`tree-idle-${activeIdleFlashToken}`} variant="fill" /> : null}
      {/* Icon: only for sub-sessions (main sessions use the tree toggle arrow) */}
      {isSub && (
        <span class="session-tree-icon" aria-hidden="true">
          {isTransport ? '☁' : '·'}
        </span>
      )}
      {!isSub && isTransport && (
        <span class="session-tree-icon" aria-hidden="true">☁</span>
      )}

      {/* Agent type badge */}
      {badge && (
        <span class="agent-badge" style={{ background: badge.color }}>
          {badge.label}
        </span>
      )}

      {/* Label */}
      <span class="session-tree-label">{label}</span>

      {/* P2P badge */}
      {inP2p && <span class="p2p-tag">{t('session.p2p_tag')}</span>}

      {/* Spacer */}
      <span class="session-tree-spacer" />

      {/* Unread count badge */}
      <UnreadBadge count={unread} />

      {/* State dot */}
      <StateDot state={state} />
    </button>
  );
}

// ── Collapse state persistence ────────────────────────────────────────────────
const LS_COLLAPSED_KEY = 'rcc_tree_collapsed';
function collapsedStorageKey(serverId?: string | null): string {
  return serverId ? `${LS_COLLAPSED_KEY}:${serverId}` : LS_COLLAPSED_KEY;
}
function loadCollapsed(serverId?: string | null): Set<string> {
  try {
    const scopedRaw = localStorage.getItem(collapsedStorageKey(serverId));
    if (scopedRaw) return new Set(JSON.parse(scopedRaw));
    const legacyRaw = localStorage.getItem(LS_COLLAPSED_KEY);
    return legacyRaw ? new Set(JSON.parse(legacyRaw)) : new Set();
  } catch {
    return new Set();
  }
}
function saveCollapsed(serverId: string | null | undefined, set: Set<string>) {
  try {
    localStorage.setItem(collapsedStorageKey(serverId), JSON.stringify([...set]));
  } catch { /* ignore */ }
}

// ── Main component ────────────────────────────────────────────────────────────
function SessionTreeInner({
  serverId,
  sessions,
  subSessions,
  activeSession,
  unreadCounts,
  idleFlashTokens,
  p2pSessionLabels,
  onSelectSession,
  onSelectSubSession,
  onNewSession,
  onNewSubSession,
}: Props) {
  const { t } = useTranslation();
  const [collapsed, setCollapsed] = useState<Set<string>>(() => loadCollapsed(serverId));

  useEffect(() => {
    setCollapsed(loadCollapsed(serverId));
  }, [serverId]);

  useEffect(() => {
    const validNames = new Set(sessions.map((session) => session.name));
    setCollapsed((prev) => {
      const next = new Set([...prev].filter((name) => validNames.has(name)));
      if (next.size === prev.size) return prev;
      saveCollapsed(serverId, next);
      return next;
    });
  }, [serverId, sessions]);

  const toggleCollapse = (name: string) => {
    setCollapsed((prev) => {
      const next = new Set(prev);
      if (next.has(name)) next.delete(name); else next.add(name);
      saveCollapsed(serverId, next);
      return next;
    });
  };

  const collapseAll = () => {
    const all = new Set(sessions.map(s => s.name));
    setCollapsed(all);
    saveCollapsed(serverId, all);
  };

  const expandAll = () => {
    setCollapsed(new Set());
    saveCollapsed(serverId, new Set());
  };

  const allCollapsed = sessions.length > 0 && sessions.every(s => collapsed.has(s.name));
  const subSessionsByParent = useMemo(() => {
    const byParent = new Map<string, SubSession[]>();
    const unparented: SubSession[] = [];
    for (const sub of subSessions) {
      if (!sub.parentSession) {
        unparented.push(sub);
        continue;
      }
      const existing = byParent.get(sub.parentSession);
      if (existing) existing.push(sub);
      else byParent.set(sub.parentSession, [sub]);
    }
    return { byParent, unparented };
  }, [subSessions]);
  // Check if any session has subs
  const hasSubs = subSessions.length > 0;

  if (sessions.length === 0) {
    return (
      <div class="session-tree-empty">
        {t('sidebar.noSessions', 'No sessions')}
      </div>
    );
  }

  return (
    <div class="session-tree" role="tree" aria-label={t('sidebar.sessionTree', 'Session tree')}>
      {/* Header: collapse toggle + new session button */}
      <div class="session-tree-header">
        {hasSubs && (
          <button
            class="session-tree-collapse-all"
            onClick={allCollapsed ? expandAll : collapseAll}
            title={allCollapsed ? t('sidebar.expand_all', 'Expand all') : t('sidebar.collapse_all', 'Collapse all')}
          >
            {allCollapsed ? '▸' : '▾'} {allCollapsed ? t('sidebar.expand_all', 'Expand all') : t('sidebar.collapse_all', 'Collapse all')}
          </button>
        )}
        {onNewSession && (
          <button class="session-tree-add-btn" data-onboarding="new-main-session" onClick={onNewSession} title={t('session.new_session', 'New session')}>+</button>
        )}
      </div>

      {sessions.map((session) => {
        const sessionLabel = getSessionLabel(session);
        const isActive = session.name === activeSession;
        const isTransport = isTransportRuntime(session);
        const unread = unreadCounts.get(session.name) ?? 0;
        const idleFlashToken = idleFlashTokens?.get(session.name) ?? 0;

        // Sub-sessions belonging to this main session. Unparented legacy
        // entries remain visible under every main session, matching the old
        // `filter(!parentSession || parentSession === session.name)` behavior
        // without doing an O(main sessions * sub-sessions) scan each render.
        const parentedChildren = subSessionsByParent.byParent.get(session.name) ?? [];
        const children = subSessionsByParent.unparented.length > 0
          ? [...parentedChildren, ...subSessionsByParent.unparented]
          : parentedChildren;
        const isCollapsed = collapsed.has(session.name);

        return (
          <div key={session.name} role="treeitem" aria-expanded={!isCollapsed && children.length > 0}>
            {/* Main session node with collapse toggle */}
            <div class="session-tree-main-row">
              {children.length > 0 && (
                <button
                  class="session-tree-toggle"
                  onClick={(e) => { e.stopPropagation(); toggleCollapse(session.name); }}
                  title={isCollapsed ? 'Expand' : 'Collapse'}
                >
                  {isCollapsed ? '▸' : '▾'}
                </button>
              )}
              <SessionNode
                label={sessionLabel}
                agentType={session.agentType}
                state={session.state}
                isActive={isActive}
                isTransport={isTransport}
                isSub={false}
                unread={unread}
                idleFlashToken={idleFlashToken}
                inP2p={!!p2pSessionLabels?.has(session.name)}
                onClick={() => onSelectSession(session.name)}
              />
              {onNewSubSession && (
                <button
                  class="session-tree-add-sub-btn"
                  onClick={(e) => { e.stopPropagation(); onSelectSession(session.name); onNewSubSession(); }}
                  title={t('session.new_sub', 'New sub-session')}
                >+</button>
              )}
            </div>

            {/* Sub-session nodes (indented) — hidden when collapsed */}
            {!isCollapsed && children.map((sub) => {
              const subLabel = sub.label ? formatLabel(sub.label) : sub.type;
              const subUnread = unreadCounts.get(sub.sessionName) ?? 0;
              const subIdleFlashToken = idleFlashTokens?.get(sub.sessionName) ?? 0;
              return (
                <SessionNode
                  key={sub.id}
                  label={subLabel}
                  agentType={sub.type}
                  state={sub.state}
                  isActive={false}
                  isTransport={false}
                  isSub={true}
                  unread={subUnread}
                  idleFlashToken={subIdleFlashToken}
                  inP2p={!!p2pSessionLabels?.has(sub.sessionName)}
                  onClick={() => onSelectSubSession(sub)}
                />
              );
            })}
          </div>
        );
      })}
    </div>
  );
}

/** Memoized export — stable keys + memoized item list avoid O(n) re-renders on WS events. */
export const SessionTree = memo(SessionTreeInner);
