/**
 * Per-project state tree — unified state for all project sub-domains.
 *
 * Uses version counter pattern (not Map reassignment) to avoid
 * Svelte 5 infinite reactive loops. Plain Map is NOT reactive;
 * _version is the sole reactive signal.
 *
 * Components read via getter functions (which read _version to subscribe)
 * and mutate via action functions (which bump _version to notify).
 */

import type {
  ProjectTab, ProjectState, FileState, CommsState, TaskState,
} from './project-state.types.ts';

export type {
  ProjectTab, ProjectState, TerminalState, FileState,
  CommsState, TaskState, TabState, DirEntry,
  TermTab, Channel, ChannelMessage, CommsAgent, DM, ChannelMember, Task,
} from './project-state.types.ts';

// ── Internal state ────────────────────────────────────────────────────────

const _projects = new Map<string, ProjectState>();
let _version = $state(0);

// ── Factory ───────────────────────────────────────────────────────────────

function createProjectState(projectId: string): ProjectState {
  const firstTabId = `${projectId}-t1`;
  return {
    terminals: {
      tabs: [{ id: firstTabId, title: 'shell 1' }],
      activeTabId: firstTabId,
      expanded: true,
      nextId: 2,
      mounted: new Set([firstTabId]),
    },
    files: {
      childrenCache: new Map(),
      openDirs: new Set(),
      loadingDirs: new Set(),
      selectedFile: null,
      fileContent: null,
      fileEncoding: 'utf8',
      fileSize: 0,
      fileError: null,
      fileLoading: false,
      isDirty: false,
      editorContent: '',
      fileRequestToken: 0,
      readMtimeMs: 0,
      showConflictDialog: false,
    },
    comms: {
      mode: 'channels',
      channels: [],
      agents: [],
      activeChannelId: null,
      activeDmAgentId: null,
      channelMessages: [],
      dmMessages: [],
      input: '',
      loading: false,
      channelMembers: [],
      showMembers: false,
    },
    tasks: {
      tasks: [],
      showCreateForm: false,
      newTitle: '',
      newDesc: '',
      newPriority: 'medium',
      error: '',
      draggedTaskId: null,
      dragOverCol: null,
      pollToken: 0,
    },
    tab: {
      activeTab: 'model',
      activatedTabs: new Set<ProjectTab>(['model']),
    },
  };
}

function ensureProject(projectId: string): ProjectState {
  let state = _projects.get(projectId);
  if (!state) {
    state = createProjectState(projectId);
    _projects.set(projectId, state);
  }
  return state;
}

// ── Generic getter (reads _version for reactivity) ────────────────────────

export function getProjectState(projectId: string): ProjectState {
  void _version;
  return ensureProject(projectId);
}

// ── Version bump (called by all actions) ──────────────────────────────────

function bump(): void { _version++; }

// ── Tab actions ───────────────────────────────────────────────────────────

export function getActiveTab(projectId: string): ProjectTab {
  void _version;
  return _projects.get(projectId)?.tab.activeTab ?? 'model';
}

export function isTabActivated(projectId: string, tab: ProjectTab): boolean {
  void _version;
  return _projects.get(projectId)?.tab.activatedTabs.has(tab) ?? (tab === 'model');
}

export function setActiveTab(projectId: string, tab: ProjectTab): void {
  const state = ensureProject(projectId);
  state.tab.activeTab = tab;
  state.tab.activatedTabs.add(tab);
  bump();
}

// ── Terminal actions ──────────────────────────────────────────────────────

export function addTerminalTab(projectId: string): void {
  const t = ensureProject(projectId).terminals;
  const id = `${projectId}-t${t.nextId}`;
  t.tabs = [...t.tabs, { id, title: `shell ${t.nextId}` }];
  t.nextId++;
  t.activeTabId = id;
  t.mounted.add(id);
  bump();
}

export function closeTerminalTab(projectId: string, tabId: string): void {
  const t = ensureProject(projectId).terminals;
  const idx = t.tabs.findIndex(tab => tab.id === tabId);
  t.tabs = t.tabs.filter(tab => tab.id !== tabId);
  if (t.activeTabId === tabId) {
    const next = t.tabs[Math.min(idx, t.tabs.length - 1)];
    t.activeTabId = next?.id ?? '';
  }
  t.mounted.delete(tabId);
  bump();
}

export function activateTerminalTab(projectId: string, tabId: string): void {
  const t = ensureProject(projectId).terminals;
  t.activeTabId = tabId;
  t.mounted.add(tabId);
  if (!t.expanded) t.expanded = true;
  bump();
}

export function toggleTerminalExpanded(projectId: string): void {
  const t = ensureProject(projectId).terminals;
  t.expanded = !t.expanded;
  bump();
}

// ── File actions ──────────────────────────────────────────────────────────

export function setFileState<K extends keyof FileState>(
  projectId: string, key: K, value: FileState[K],
): void {
  const f = ensureProject(projectId).files;
  f[key] = value;
  bump();
}

export function setFileMulti(
  projectId: string, updates: Partial<FileState>,
): void {
  const f = ensureProject(projectId).files;
  for (const [k, v] of Object.entries(updates)) {
    (f as unknown as Record<string, unknown>)[k] = v;
  }
  bump();
}

export function nextFileRequestToken(projectId: string): number {
  const f = ensureProject(projectId).files;
  return ++f.fileRequestToken;
}

export function getFileRequestToken(projectId: string): number {
  void _version;
  return ensureProject(projectId).files.fileRequestToken;
}

// ── Comms actions ─────────────────────────────────────────────────────────

export function setCommsState<K extends keyof CommsState>(
  projectId: string, key: K, value: CommsState[K],
): void {
  const c = ensureProject(projectId).comms;
  c[key] = value;
  bump();
}

export function setCommsMulti(
  projectId: string, updates: Partial<CommsState>,
): void {
  const c = ensureProject(projectId).comms;
  for (const [k, v] of Object.entries(updates)) {
    (c as unknown as Record<string, unknown>)[k] = v;
  }
  bump();
}

// ── Task actions ──────────────────────────────────────────────────────────

export function setTaskState<K extends keyof TaskState>(
  projectId: string, key: K, value: TaskState[K],
): void {
  const t = ensureProject(projectId).tasks;
  t[key] = value;
  bump();
}

export function setTaskMulti(
  projectId: string, updates: Partial<TaskState>,
): void {
  const t = ensureProject(projectId).tasks;
  for (const [k, v] of Object.entries(updates)) {
    (t as unknown as Record<string, unknown>)[k] = v;
  }
  bump();
}

export function nextTaskPollToken(projectId: string): number {
  const t = ensureProject(projectId).tasks;
  return ++t.pollToken;
}

export function getTaskPollToken(projectId: string): number {
  void _version;
  return ensureProject(projectId).tasks.pollToken;
}

// ── Cleanup ───────────────────────────────────────────────────────────────

export function removeProject(projectId: string): void {
  if (_projects.delete(projectId)) bump();
}
