// Tests for Electrobun workspace-store — pure logic.
// Uses bun:test. Tests CRUD operations and derived state logic.

import { describe, it, expect, beforeEach } from 'bun:test';

// ── Replicated types ──────────────────────────────────────────────────────────

interface Project {
  id: string;
  name: string;
  cwd: string;
  accent: string;
  status: 'running' | 'idle' | 'stalled';
  costUsd: number;
  tokens: number;
  messages: Array<{ id: number; role: string; content: string }>;
  provider?: string;
  groupId?: string;
  cloneOf?: string;
}

interface Group {
  id: string;
  name: string;
  icon: string;
  position: number;
}

const ACCENTS = [
  'var(--ctp-mauve)', 'var(--ctp-sapphire)', 'var(--ctp-teal)',
  'var(--ctp-peach)', 'var(--ctp-pink)', 'var(--ctp-lavender)',
  'var(--ctp-green)', 'var(--ctp-blue)', 'var(--ctp-flamingo)',
];

// ── Pure store logic (no runes, no RPC) ──────────────────────────────────────

function createWorkspaceState() {
  let projects: Project[] = [];
  let groups: Group[] = [{ id: 'dev', name: 'Development', icon: '1', position: 0 }];
  let activeGroupId = 'dev';
  let previousGroupId: string | null = null;

  return {
    getProjects: () => projects,
    getGroups: () => groups,
    getActiveGroupId: () => activeGroupId,
    getMountedGroupIds: () => new Set([activeGroupId, ...(previousGroupId ? [previousGroupId] : [])]),
    getActiveGroup: () => groups.find(g => g.id === activeGroupId) ?? groups[0],
    getFilteredProjects: () => projects.filter(p => (p.groupId ?? 'dev') === activeGroupId),

    addProject(name: string, cwd: string): void {
      if (!name.trim() || !cwd.trim()) return;
      const id = `p-${Date.now()}`;
      const accent = ACCENTS[projects.length % ACCENTS.length];
      projects = [...projects, {
        id, name: name.trim(), cwd: cwd.trim(), accent,
        status: 'idle', costUsd: 0, tokens: 0, messages: [],
        provider: 'claude', groupId: activeGroupId,
      }];
    },

    deleteProject(projectId: string): void {
      projects = projects.filter(p => p.id !== projectId);
    },

    cloneCountForProject(projectId: string): number {
      return projects.filter(p => p.cloneOf === projectId).length;
    },

    addGroup(name: string): void {
      if (!name.trim()) return;
      const id = `grp-${Date.now()}`;
      const position = groups.length;
      groups = [...groups, { id, name: name.trim(), icon: String(position + 1), position }];
    },

    setActiveGroup(id: string): void {
      if (activeGroupId !== id) previousGroupId = activeGroupId;
      activeGroupId = id;
    },

    getTotalCost: () => projects.reduce((s, p) => s + p.costUsd, 0),
    getTotalTokens: () => projects.reduce((s, p) => s + p.tokens, 0),
  };
}

// ── Tests ───────────────────────────────────────────────────────────────────

describe('workspace store — project CRUD', () => {
  let ws: ReturnType<typeof createWorkspaceState>;

  beforeEach(() => {
    ws = createWorkspaceState();
  });

  it('starts with empty projects', () => {
    expect(ws.getProjects()).toHaveLength(0);
  });

  it('addProject creates a project with correct defaults', () => {
    ws.addProject('My Project', '/home/user/code');
    const projects = ws.getProjects();
    expect(projects).toHaveLength(1);
    expect(projects[0].name).toBe('My Project');
    expect(projects[0].cwd).toBe('/home/user/code');
    expect(projects[0].status).toBe('idle');
    expect(projects[0].provider).toBe('claude');
    expect(projects[0].groupId).toBe('dev');
  });

  it('addProject trims whitespace', () => {
    ws.addProject('  padded  ', '  /path  ');
    expect(ws.getProjects()[0].name).toBe('padded');
    expect(ws.getProjects()[0].cwd).toBe('/path');
  });

  it('addProject rejects empty name or cwd', () => {
    ws.addProject('', '/path');
    ws.addProject('name', '');
    ws.addProject('  ', '  ');
    expect(ws.getProjects()).toHaveLength(0);
  });

  it('addProject assigns accent colors cyclically', () => {
    for (let i = 0; i < ACCENTS.length + 1; i++) {
      ws.addProject(`P${i}`, `/p${i}`);
    }
    const projects = ws.getProjects();
    expect(projects[0].accent).toBe(ACCENTS[0]);
    expect(projects[ACCENTS.length].accent).toBe(ACCENTS[0]); // wraps around
  });

  it('deleteProject removes the project', () => {
    ws.addProject('A', '/a');
    const id = ws.getProjects()[0].id;
    ws.deleteProject(id);
    expect(ws.getProjects()).toHaveLength(0);
  });

  it('deleteProject ignores unknown id', () => {
    ws.addProject('A', '/a');
    ws.deleteProject('nonexistent');
    expect(ws.getProjects()).toHaveLength(1);
  });

  it('cloneCountForProject counts clones', () => {
    ws.addProject('Main', '/main');
    const mainId = ws.getProjects()[0].id;
    // Manually add clones (addProject doesn't set cloneOf)
    const projects = ws.getProjects();
    projects.push({
      id: 'clone-1', name: 'Clone 1', cwd: '/clone1', accent: ACCENTS[0],
      status: 'idle', costUsd: 0, tokens: 0, messages: [],
      cloneOf: mainId,
    });
    expect(ws.cloneCountForProject(mainId)).toBe(1);
  });
});

describe('workspace store — group CRUD', () => {
  let ws: ReturnType<typeof createWorkspaceState>;

  beforeEach(() => {
    ws = createWorkspaceState();
  });

  it('starts with default Development group', () => {
    expect(ws.getGroups()).toHaveLength(1);
    expect(ws.getGroups()[0].name).toBe('Development');
  });

  it('addGroup creates group with incremented position', () => {
    ws.addGroup('Production');
    expect(ws.getGroups()).toHaveLength(2);
    expect(ws.getGroups()[1].name).toBe('Production');
    expect(ws.getGroups()[1].position).toBe(1);
    expect(ws.getGroups()[1].icon).toBe('2');
  });

  it('addGroup rejects empty name', () => {
    ws.addGroup('');
    ws.addGroup('  ');
    expect(ws.getGroups()).toHaveLength(1);
  });

  it('activeGroup defaults to first group', () => {
    expect(ws.getActiveGroup().id).toBe('dev');
  });

  it('setActiveGroup changes active and records previous', () => {
    ws.addGroup('Staging');
    const stagingId = ws.getGroups()[1].id;
    ws.setActiveGroup(stagingId);
    expect(ws.getActiveGroupId()).toBe(stagingId);
    // mountedGroupIds should include both active and previous
    expect(ws.getMountedGroupIds().has('dev')).toBe(true);
    expect(ws.getMountedGroupIds().has(stagingId)).toBe(true);
  });
});

describe('workspace store — derived state', () => {
  let ws: ReturnType<typeof createWorkspaceState>;

  beforeEach(() => {
    ws = createWorkspaceState();
  });

  it('filteredProjects returns only active group projects', () => {
    ws.addProject('DevProject', '/dev');
    ws.addGroup('Staging');
    const stagingId = ws.getGroups()[1].id;
    ws.setActiveGroup(stagingId);
    ws.addProject('StagingProject', '/staging');

    // Switch back to dev
    ws.setActiveGroup('dev');
    const filtered = ws.getFilteredProjects();
    expect(filtered).toHaveLength(1);
    expect(filtered[0].name).toBe('DevProject');
  });

  it('mountedGroupIds only includes active + previous', () => {
    // Use deterministic IDs to avoid Date.now() collisions
    const state = createWorkspaceState();
    // Manually push groups with known IDs
    const groups = state.getGroups();
    groups.push({ id: 'grp-aaa', name: 'G1', icon: '2', position: 1 });
    groups.push({ id: 'grp-bbb', name: 'G2', icon: '3', position: 2 });

    state.setActiveGroup('grp-aaa');
    state.setActiveGroup('grp-bbb');
    const mounted = state.getMountedGroupIds();
    expect(mounted.size).toBe(2);
    expect(mounted.has('grp-bbb')).toBe(true);   // active
    expect(mounted.has('grp-aaa')).toBe(true);   // previous
    expect(mounted.has('dev')).toBe(false);        // two switches ago — not mounted
  });
});

describe('workspace store — aggregates', () => {
  it('getTotalCost sums across projects', () => {
    const ws = createWorkspaceState();
    ws.addProject('A', '/a');
    ws.addProject('B', '/b');
    // Mutate costs directly
    ws.getProjects()[0].costUsd = 1.50;
    ws.getProjects()[1].costUsd = 2.75;
    expect(ws.getTotalCost()).toBeCloseTo(4.25, 2);
  });

  it('getTotalTokens sums across projects', () => {
    const ws = createWorkspaceState();
    ws.addProject('A', '/a');
    ws.addProject('B', '/b');
    ws.getProjects()[0].tokens = 10000;
    ws.getProjects()[1].tokens = 25000;
    expect(ws.getTotalTokens()).toBe(35000);
  });
});
