import { useEffect, useState } from 'react';
import { api } from '@/api/client';
import type { SidebarProject, SidebarResponse, SidebarSession } from '@/api/types';
import { sessionEvents } from './sessionEvents';

const RECENT_SESSIONS_LIMIT = 6;

// Module-scope shared state — there is at most one sidebar fetch
// for the lifetime of the page, regardless of how many components
// call useSidebar().
let sharedData: SidebarResponse | null = null;
let sharedError: string | null = null;
let sharedLoading: boolean = true;
let initialized = false;
let fetchInFlight: Promise<void> | null = null;
let activeSession: { session_id: string; project_id: string } | null = null;
const subscribers = new Set<() => void>();

function notify(): void {
  for (const sub of subscribers) {
    try {
      sub();
    } catch {
      // swallow — one bad subscriber shouldn't break others
    }
  }
}

function load(): Promise<void> {
  if (fetchInFlight) return fetchInFlight;
  sharedLoading = true;
  sharedError = null;
  notify();
  const p = (async () => {
    try {
      const res = await api.sidebar.get();
      sharedData = res;
      sharedError = null;
    } catch (err) {
      sharedData = null;
      sharedError = err instanceof Error ? err.message : 'failed to load sidebar';
    } finally {
      sharedLoading = false;
      fetchInFlight = null;
      notify();
    }
  })();
  fetchInFlight = p;
  return p;
}

function applyEvent(prev: SidebarResponse, event: import('./sessionEvents').SessionEvent): SidebarResponse {
  switch (event.type) {
    case 'project_created': {
      if (prev.projects.some((p) => p.id === event.project.id)) return prev;
      const fresh: SidebarProject = {
        id: event.project.id,
        name: event.project.name,
        path: event.project.path,
        gitea_remote: event.project.gitea_remote ?? null,
        recent_sessions: [],
        total_sessions: 0,
      };
      return { ...prev, projects: [fresh, ...prev.projects] };
    }
    case 'project_deleted': {
      const next = prev.projects.filter((p) => p.id !== event.project_id);
      if (next.length === prev.projects.length) return prev;
      return { ...prev, projects: next };
    }
    case 'session_created': {
      let changed = false;
      const projects = prev.projects.map((p) => {
        if (p.id !== event.project_id) return p;
        if (p.recent_sessions.some((s) => s.id === event.session.id)) return p;
        changed = true;
        const fresh: SidebarSession = {
          id: event.session.id,
          name: event.session.name,
          model: event.session.model,
          updated_at: event.session.updated_at,
          project_id: event.project_id,
        };
        return {
          ...p,
          recent_sessions: [fresh, ...p.recent_sessions].slice(0, RECENT_SESSIONS_LIMIT),
          total_sessions: p.total_sessions + 1,
        };
      });
      return changed ? { ...prev, projects } : prev;
    }
    case 'session_deleted': {
      let changed = false;
      const projects = prev.projects.map((p) => {
        if (p.id !== event.project_id) return p;
        const recent = p.recent_sessions.filter((s) => s.id !== event.session_id);
        const wasPresent = recent.length !== p.recent_sessions.length;
        if (!wasPresent) return p;
        changed = true;
        return {
          ...p,
          recent_sessions: recent,
          total_sessions: Math.max(0, p.total_sessions - 1),
        };
      });
      return changed ? { ...prev, projects } : prev;
    }
    case 'session_renamed': {
      let changed = false;
      const projects = prev.projects.map((p) => {
        let projectChanged = false;
        const recent = p.recent_sessions.map((s) => {
          if (s.id !== event.session_id) return s;
          if (s.name === event.name) return s;
          projectChanged = true;
          return { ...s, name: event.name };
        });
        if (!projectChanged) return p;
        changed = true;
        return { ...p, recent_sessions: recent };
      });
      return changed ? { ...prev, projects } : prev;
    }
    case 'session_updated': {
      let changed = false;
      const projects = prev.projects.map((p) => {
        if (p.id !== event.project_id) return p;
        let projectChanged = false;
        const recent = p.recent_sessions.map((s) => {
          if (s.id !== event.session_id) return s;
          projectChanged = true;
          return { ...s, name: event.name, updated_at: event.updated_at };
        });
        if (!projectChanged) return p;
        changed = true;
        const sorted = [...recent].sort(
          (a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime()
        );
        return { ...p, recent_sessions: sorted };
      });
      return changed ? { ...prev, projects } : prev;
    }
    case 'session_loaded':
      // activeSessionProjectId is updated in the subscribe callback; no data change here.
      return prev;
    case 'session_workspace_updated':
      // Pane layout is consumed by useWorkspacePanes; sidebar has no stake.
      return prev;
    case 'open_file_in_browser':
      // Consumed by Workspace (T7); no sidebar state change needed.
      return prev;
    case 'attach_chat_file':
      return prev;
    case 'open_chat_in_active_pane':
    case 'open_chat_in_new_pane':
      // Consumed by Workspace; sidebar has no business with pane state.
      return prev;
    case 'open_markdown_artifact_pane':
    case 'open_html_artifact_pane':
      // v1.14.x-html-artifact-panes: consumed by useWorkspacePanes; sidebar
      // has no business with pane state.
      return prev;
    case 'open_settings_pane':
      // Consumed by Session.tsx (calls toggleSettingsPane on its panesHook).
      // Sidebar data is untouched.
      return prev;
    case 'session_archived': {
      let changed = false;
      const projects = prev.projects.map((p) => {
        if (p.id !== event.project_id) return p;
        const recent = p.recent_sessions.filter((s) => s.id !== event.session_id);
        if (recent.length === p.recent_sessions.length) return p;
        changed = true;
        return {
          ...p,
          recent_sessions: recent,
          total_sessions: Math.max(0, p.total_sessions - 1),
        };
      });
      return changed ? { ...prev, projects } : prev;
    }
    case 'chat_created':
    case 'chat_updated':
    case 'chat_archived':
    case 'chat_unarchived':
    case 'chat_deleted':
    case 'chat_status':
    case 'refetch_messages':
    case 'git_diff_refresh':
      // Consumed by useGitDiff; no sidebar state change needed.
      return prev;
    case 'open_orchestrator_pane':
    case 'open_flow_launcher':
    case 'flow_run_started':
    case 'flow_run_step_updated':
      // Consumed by useWorkspacePanes / OrchestratorPane / FlowLauncherDialog; sidebar has no stake.
      return prev;
    case 'open_arena_launcher':
    case 'open_arena_pane':
    case 'battle_started':
    case 'contestant_updated':
    case 'battle_updated':
      // Consumed by useWorkspacePanes / ArenaPane / ArenaLauncherDialog; sidebar has no stake.
      return prev;
    case 'collision_warning':
    case 'agent_message':
      // Published by BooCoder on the coder user channel; sidebar has no stake.
      return prev;
    case 'project_archived': {
      const next = prev.projects.filter((p) => p.id !== event.project_id);
      if (next.length === prev.projects.length) return prev;
      return { ...prev, projects: next };
    }
    case 'project_unarchived': {
      if (prev.projects.some((p) => p.id === event.project.id)) return prev;
      const fresh: SidebarProject = {
        id: event.project.id,
        name: event.project.name,
        path: event.project.path,
        gitea_remote: event.project.gitea_remote ?? null,
        recent_sessions: [],
        total_sessions: 0,
      };
      return { ...prev, projects: [fresh, ...prev.projects] };
    }
    case 'project_updated': {
      let changed = false;
      const projects = prev.projects.map((p) => {
        if (p.id !== event.project_id) return p;
        if (p.name === event.name) return p;
        changed = true;
        return { ...p, name: event.name };
      });
      return changed ? { ...prev, projects } : prev;
    }
    default:
      return prev;
  }
}

// One bus subscription for the lifetime of the module. Events arriving
// before the initial fetch resolves are dropped; the eventual fetch
// result is the source of truth.
// Guard prevents duplicate listeners during Vite HMR reloads.
const G = globalThis as Record<string, unknown>;
if (!G.__boocode_sidebar_subscribed) {
  G.__boocode_sidebar_subscribed = true;
  sessionEvents.subscribe((event) => {
    if (event.type === 'session_loaded') {
      activeSession = { session_id: event.session_id, project_id: event.project_id };
      notify();
      return;
    }
    if (!sharedData) return;
    const next = applyEvent(sharedData, event);
    if (next === sharedData) return;
    sharedData = next;
    notify();
  });
}

interface Snapshot {
  data: SidebarResponse | null;
  error: string | null;
  loading: boolean;
  activeSession: { session_id: string; project_id: string } | null;
}

function snapshot(): Snapshot {
  return { data: sharedData, error: sharedError, loading: sharedLoading, activeSession };
}

export function useSidebar(): {
  data: SidebarResponse | null;
  error: string | null;
  loading: boolean;
  retry: () => void;
  activeSession: { session_id: string; project_id: string } | null;
} {
  const [state, setState] = useState<Snapshot>(snapshot);

  useEffect(() => {
    const sub = () => setState(snapshot());
    subscribers.add(sub);
    // Sync up if the module state changed between render and effect.
    sub();
    if (!initialized) {
      initialized = true;
      void load();
    }
    return () => {
      subscribers.delete(sub);
    };
  }, []);

  const retry = () => {
    void load();
  };

  return { data: state.data, error: state.error, loading: state.loading, retry, activeSession: state.activeSession };
}
