// simulation.jsx
// Data model for Agent City + tick loop driving agent movement.
//
// The building is a stack of FLOORS. Each floor is one of:
//   - room  (a department/workspace agents path to)
//   - lobby (ground floor, also where humans enter)
//   - sub   (substrate basement floors — visualizing the multiplayer OS)
// A central elevator shaft runs the full height. Agents pick a target floor,
// walk to the elevator, ride to that floor, walk to a workstation, "work"
// for a few seconds, then pick a new target. Humans behave the same.
//
// Coords are in tile units. 1 tile = TILE px (set in building.jsx).
// Floors are indexed 0=top, increasing downward, but we render top-down so
// floor 0 is the highest visible floor. Substrate floors have negative y in
// "world units" but we just stack them visually under the lobby.

const TILE = 14;          // pixels per tile (chunky pixel feel)
const FLOOR_H = 6;        // tiles per floor (interior height ~84px)
const FLOOR_W = 38;       // tiles wide (building width ~532px)
const ELEV_X = 18;        // elevator left edge in tiles (centered-ish)
const ELEV_W = 4;         // elevator car width in tiles

// ---------------------------------------------------------------------------
// FLOORS — top to bottom
// kind: 'room' | 'lobby' | 'sub'
// accent: tints for floor stripe + workstation glow
// stations: x positions (in tiles) where agents can "work"
// ---------------------------------------------------------------------------

const FLOORS = [
  // Roof beacon
  { id: 'roof', kind: 'roof', label: 'GUMBO HQ', sublabel: 'EST. 2024', stations: [] },

  // Top: leadership / planning
  { id: 'strategy', kind: 'room', label: 'Strategy Loft',
    code: '12F', dept: 'leadership',
    accent: 'orange', stations: [6, 11, 27, 32],
    desc: 'Quarterly planning, OKRs, agent fleet roadmap.' },

  { id: 'research', kind: 'room', label: 'Research Lab',
    code: '11F', dept: 'research',
    accent: 'pine', stations: [5, 10, 14, 27, 32],
    desc: 'Evals, model probes, capability mapping.' },

  { id: 'design', kind: 'room', label: 'Design Studio',
    code: '10F', dept: 'design',
    accent: 'cayenne', stations: [6, 11, 26, 31],
    desc: 'Interfaces for humans + agents. Halftones included.' },

  { id: 'eng-a', kind: 'room', label: 'Engineering — Builders',
    code: '09F', dept: 'engineering',
    accent: 'blue', stations: [4, 9, 14, 26, 31],
    desc: 'Ship features. Agents pair with humans.' },

  { id: 'eng-b', kind: 'room', label: 'Engineering — Reviewers',
    code: '08F', dept: 'engineering',
    accent: 'blue', stations: [5, 10, 27, 32],
    desc: 'Code review, security, infra hardening.' },

  { id: 'kitchen', kind: 'room', label: 'The Kitchen',
    code: '07F', dept: 'social',
    accent: 'orange', stations: [7, 13, 26, 31],
    desc: 'Recipes, retros, the actual coffee bar.' },

  { id: 'sales', kind: 'room', label: 'Sales Floor',
    code: '06F', dept: 'sales',
    accent: 'okra', stations: [4, 9, 14, 26, 31],
    desc: 'Outbound, demos, contract drafting.' },

  { id: 'cs', kind: 'room', label: 'Customer Success',
    code: '05F', dept: 'support',
    accent: 'okra', stations: [5, 10, 27, 32],
    desc: 'Triage, onboarding agents, customer ops.' },

  { id: 'ops', kind: 'room', label: 'Operations',
    code: '04F', dept: 'ops',
    accent: 'pine', stations: [6, 11, 26, 31],
    desc: 'Finance, HR, vendor agents.' },

  { id: 'data', kind: 'room', label: 'Data & Eval',
    code: '03F', dept: 'data',
    accent: 'cayenne', stations: [5, 10, 14, 27, 32],
    desc: 'Telemetry, dashboards, agent KPIs.' },

  { id: 'meeting', kind: 'room', label: 'Roux Room',
    code: '02F', dept: 'meeting',
    accent: 'orange', stations: [10, 14, 22, 26],
    desc: 'Joint sessions: humans + agents in the loop.' },

  // Lobby
  { id: 'lobby', kind: 'lobby', label: 'Lobby',
    code: '01F', dept: 'lobby',
    accent: 'blue', stations: [3, 8, 30, 34],
    desc: 'Where the humans walk in.' },

  // ─── Substrate (basement) ───
  { id: 'bus', kind: 'sub', label: 'Message Bus',
    code: 'B1', dept: 'substrate',
    accent: 'blue', stations: [],
    desc: 'Streaming layer routing agent ↔ agent ↔ human messages.' },

  { id: 'memory', kind: 'sub', label: 'Shared Memory',
    code: 'B2', dept: 'substrate',
    accent: 'blue', stations: [],
    desc: 'Long-term knowledge graph. Versioned, queryable, cited.' },

  { id: 'skills', kind: 'sub', label: 'Skills Registry',
    code: 'B3', dept: 'substrate',
    accent: 'blue', stations: [],
    desc: 'Tools and skills any agent can equip.' },

  { id: 'identity', kind: 'sub', label: 'Identity & Trust',
    code: 'B4', dept: 'substrate',
    accent: 'blue', stations: [],
    desc: 'Who can do what. Audit trail, permissions, signing.' },
];

// helpers --------------------------------------------------------------------

function floorIndex(id) {
  return FLOORS.findIndex(f => f.id === id);
}
function workableFloors() {
  return FLOORS.filter(f => f.kind === 'room' || f.kind === 'lobby');
}

// ---------------------------------------------------------------------------
// AGENTS — both AI and human. type: 'agent' | 'human'
// role + name set tone for inspector. home is where they idle.
// ---------------------------------------------------------------------------

const AGENT_NAMES = [
  ['Roux-7',     'agent', 'Code Reviewer',     'eng-b',    'pine'],
  ['Étouffée-2', 'agent', 'Researcher',        'research', 'pine'],
  ['Beignet',    'agent', 'Designer Pair',     'design',   'cayenne'],
  ['Andouille',  'agent', 'Builder',           'eng-a',    'blue'],
  ['Mirepoix',   'agent', 'Builder',           'eng-a',    'blue'],
  ['Filé-3',     'agent', 'Data Analyst',      'data',     'cayenne'],
  ['Crawfish',   'agent', 'CS Triage',         'cs',       'okra'],
  ['Praline',    'agent', 'Sales SDR',         'sales',    'okra'],
  ['Holy-Trinity','agent','Strategy Agent',    'strategy', 'orange'],
  ['Gumbo-1',    'agent', 'Orchestrator',      'meeting',  'orange'],
  ['Tabasco',    'agent', 'Eval Bot',          'data',     'cayenne'],
  ['Okra-9',     'agent', 'Builder',           'eng-a',    'blue'],
  ['Bisque',     'agent', 'Researcher',        'research', 'pine'],
  ['Jambalaya',  'agent', 'Ops Agent',         'ops',      'pine'],
  ['Sazerac',    'agent', 'Sales SDR',         'sales',    'okra'],

  ['Ada',        'human', 'Founder',           'strategy', 'orange'],
  ['Marcus',     'human', 'Engineer',          'eng-a',    'blue'],
  ['Priya',      'human', 'Designer',          'design',   'cayenne'],
  ['Kenji',      'human', 'CS Lead',           'cs',       'okra'],
  ['Soraya',     'human', 'Researcher',        'research', 'pine'],
  ['Theo',       'human', 'AE',                'sales',    'okra'],
];

function makeAgents() {
  return AGENT_NAMES.map(([name, type, role, homeId, color], i) => {
    const home = floorIndex(homeId);
    const floor = FLOORS[home];
    const station = floor.stations[i % floor.stations.length] || 8;
    return {
      id: 'a' + i,
      name, type, role, color,
      home: homeId,
      // current state
      floor: home,            // current floor index
      x: station,             // current x in tiles (within floor)
      tx: station,            // target x within floor
      targetFloor: home,      // floor we want to be on
      state: 'work',          // 'work' | 'idle' | 'walk' | 'wait-elev' | 'in-elev' | 'arrive'
      activity: 'working',    // 'working' or idle flavor: sitting/lounging/strolling/coffee/reading
      task: pickTask(role),
      progress: Math.random(),
      mood: 0.6 + Math.random() * 0.4,
      // animation
      step: 0,                // walk step counter (for sprite bob)
      cooldown: 60 + Math.floor(Math.random() * 240),
    };
  });
}

const TASKS = {
  'Code Reviewer':   ['reviewing PR #482', 'flagging an unsafe diff', 'leaving 3 comments', 'approving #501'],
  'Researcher':      ['probing eval set', 'reading arxiv:2511.0042', 'writing memo', 'running ablation'],
  'Designer Pair':   ['drafting layout', 'pulling halftone', 'prototyping flow', 'pairing with Priya'],
  'Builder':         ['implementing /api/teams', 'writing test', 'pushing branch', 'fixing flake'],
  'Data Analyst':    ['querying telemetry', 'building dashboard', 'spotting regression', 'writing SQL'],
  'CS Triage':       ['routing ticket #2031', 'answering customer', 'escalating to Kenji', 'updating runbook'],
  'Sales SDR':       ['drafting outbound', 'researching account', 'logging call', 'scheduling demo'],
  'Strategy Agent':  ['simulating Q3 plan', 'comparing OKRs', 'briefing Ada', 'reading board memo'],
  'Orchestrator':    ['routing 4 tasks', 'spawning subagent', 'reconciling state', 'pinging humans'],
  'Eval Bot':        ['running eval suite', 'comparing models', 'logging metric'],
  'Ops Agent':       ['reconciling invoice', 'filing expense', 'syncing HR'],
  'Founder':         ['writing all-hands', 'reviewing fleet', 'prepping board'],
  'Engineer':        ['coding /substrate', 'pairing with Roux-7', 'on standup'],
  'Designer':        ['sketching cutaway', 'reviewing PRs', 'critique'],
  'CS Lead':         ['1:1 with Crawfish', 'reading tickets', 'training agent'],
  'AE':              ['demo prep', 'emailing prospect', 'forecast'],
};
function pickTask(role) {
  const list = TASKS[role] || ['working'];
  return list[Math.floor(Math.random() * list.length)];
}

const IDLE_ACTIVITIES = ['sitting', 'lounging', 'strolling', 'coffee', 'reading'];
const WORK_LIVE_STATES = new Set(['active', 'running', 'connected', 'live']);
const IDLE_LIVE_STATES = new Set(['scheduled', 'paused', 'archived', 'stopped', 'offline', 'unknown']);

function hashString(value) {
  let h = 0;
  for (const ch of String(value || '')) h = (h * 31 + ch.charCodeAt(0)) % 1000003;
  return h;
}
function pickIdleActivity(agent) {
  const seed = hashString(`${agent.id || agent.name}:${Date.now() >> 15}`);
  return IDLE_ACTIVITIES[seed % IDLE_ACTIVITIES.length];
}
function isActuallyWorking(agent) {
  const live = String(agent.liveState || agent.live_state || '').toLowerCase();
  if (WORK_LIVE_STATES.has(live)) return true;
  if (IDLE_LIVE_STATES.has(live)) return false;
  return agent.state === 'work' && agent.activity === 'working';
}
function idleFloorFor(agent) {
  const preferred = ['kitchen', 'lobby', 'meeting', 'strategy'];
  const seed = hashString(agent.id || agent.name);
  const id = preferred[seed % preferred.length];
  const idx = floorIndex(id);
  return idx >= 0 ? idx : floorIndex(agent.home || 'lobby');
}
function stationForFloor(floorIdx, agent) {
  const f = FLOORS[floorIdx] || FLOORS[floorIndex('lobby')];
  const stations = f.stations && f.stations.length ? f.stations : [6, 10, 26, 32];
  return stations[hashString(`${agent.id || agent.name}:${floorIdx}`) % stations.length] || 8;
}

// ---------------------------------------------------------------------------
// TICK — moves agents one step. Called ~30/sec * speed.
// ---------------------------------------------------------------------------

function tickAgents(agents, elevators) {
  const walkable = workableFloors();
  return agents.map(a => {
    let { floor, x, tx, targetFloor, state, step, cooldown, progress } = a;
    let activity = a.activity || (isActuallyWorking(a) ? 'working' : pickIdleActivity(a));
    const workingNow = isActuallyWorking({ ...a, state, activity });

    // Tick task progress only when the underlying Hermes thing is actually doing work.
    if (workingNow && state === 'work') progress = Math.min(1, progress + 0.006);
    if (!workingNow && state === 'work') {
      state = 'idle';
      activity = pickIdleActivity(a);
      cooldown = 180 + Math.floor(Math.random() * 420);
    }
    if (workingNow && state === 'idle') {
      state = 'work';
      activity = 'working';
      targetFloor = floorIndex(a.home);
      tx = stationForFloor(targetFloor, a);
      a.task = pickTask(a.role);
      cooldown = 180 + Math.floor(Math.random() * 360);
    }

    if (state === 'work') {
      activity = 'working';
      cooldown -= 1;
      if (cooldown <= 0) {
        // Working agents still travel for real reasons: pairing, review, meetings, customer handoff.
        const stayHome = Math.random() < 0.58;
        let nextFloor;
        if (stayHome) {
          nextFloor = floorIndex(a.home);
        } else {
          const candidates = walkable.filter(f => floorIndex(f.id) !== floor);
          nextFloor = floorIndex(candidates[Math.floor(Math.random() * candidates.length)].id);
        }
        targetFloor = nextFloor;
        tx = targetFloor === floor ? stationForFloor(targetFloor, a) : ELEV_X + 1;
        state = 'walk';
        cooldown = 0;
      }
    } else if (state === 'idle') {
      cooldown -= 1;
      if (activity === 'strolling') {
        step += 1;
        const leftBound = 4;
        const rightBound = window.FLOOR_W ? window.FLOOR_W - 5 : FLOOR_W - 5;
        if (!tx || Math.abs(tx - x) < 0.2) tx = leftBound + Math.random() * (rightBound - leftBound);
        const dir = Math.sign(tx - x);
        x += dir * 0.05;
      }
      if (cooldown <= 0) {
        const nextActivity = pickIdleActivity(a);
        const nextFloor = Math.random() < 0.42 ? idleFloorFor(a) : floorIndex(a.home);
        activity = nextActivity;
        targetFloor = nextFloor;
        tx = targetFloor === floor ? stationForFloor(targetFloor, a) : ELEV_X + 1;
        state = targetFloor === floor ? 'idle' : 'walk';
        cooldown = 240 + Math.floor(Math.random() * 720);
      }
    } else if (state === 'walk') {
      step += 1;
      const dir = Math.sign(tx - x);
      x += dir * 0.12;
      if (Math.abs(tx - x) < 0.15) {
        x = tx;
        if (targetFloor !== floor) {
          state = 'wait-elev';
          elevators.requests.add(floor);
        } else if (workingNow) {
          state = 'work';
          activity = 'working';
          progress = 0;
          a.task = pickTask(a.role);
          cooldown = 240 + Math.floor(Math.random() * 600);
        } else {
          state = 'idle';
          activity = activity === 'working' ? pickIdleActivity(a) : activity;
          cooldown = 240 + Math.floor(Math.random() * 720);
        }
      }
    } else if (state === 'wait-elev') {
      activity = workingNow ? 'working' : 'standing';
      if (elevators.floor === floor && elevators.passengers.size < 6) {
        elevators.passengers.add(a.id);
        state = 'in-elev';
      }
    } else if (state === 'in-elev') {
      floor = elevators.floor;
      x = ELEV_X + 1.5;
      if (elevators.floor === targetFloor) {
        elevators.passengers.delete(a.id);
        state = 'walk';
        tx = stationForFloor(targetFloor, a);
      }
    }

    return { ...a, floor, x, tx, targetFloor, state, activity, step, cooldown, progress };
  });
}

// Elevator: simple controller that visits requested floors in turn.
function tickElevator(elev, agents) {
  let { floor, dir, dwell, requests, passengers, target } = elev;

  if (dwell > 0) { dwell -= 1; return { ...elev, dwell }; }

  // Determine next target if none
  if (target === null) {
    // Prefer destinations of riders
    const passDests = new Set();
    agents.forEach(a => { if (passengers.has(a.id)) passDests.add(a.targetFloor); });
    if (passDests.size > 0) {
      target = nearest(floor, passDests);
    } else if (requests.size > 0) {
      target = nearest(floor, requests);
    }
  }

  if (target === null) return { ...elev, target: null };

  if (floor < target) { floor += 1; dir = 1; }
  else if (floor > target) { floor -= 1; dir = -1; }

  if (floor === target) {
    dwell = 30;
    requests.delete(floor);
    target = null;
  }
  return { ...elev, floor, dir, dwell, target };
}

function nearest(from, set) {
  let best = null, bestD = Infinity;
  for (const f of set) {
    const d = Math.abs(f - from);
    if (d < bestD) { bestD = d; best = f; }
  }
  return best;
}

// Substrate "packets" — animated dots flowing along basement floors to
// represent message-bus / memory-write activity. Pure visual life.
function makePackets(n = 24) {
  return Array.from({ length: n }, () => ({
    id: Math.random(),
    floor: floorIndex(['bus','memory','skills','identity'][Math.floor(Math.random()*4)]),
    x: Math.random() * FLOOR_W,
    vx: (Math.random() < 0.5 ? -1 : 1) * (0.05 + Math.random() * 0.08),
    color: ['blue','blue','blue','okra','cayenne'][Math.floor(Math.random()*5)],
  }));
}

function tickPackets(packets) {
  return packets.map(p => {
    let x = p.x + p.vx;
    if (x < 0) x = FLOOR_W;
    if (x > FLOOR_W) x = 0;
    return { ...p, x };
  });
}

Object.assign(window, {
  TILE, FLOOR_H, FLOOR_W, ELEV_X, ELEV_W,
  FLOORS, floorIndex, workableFloors,
  makeAgents, tickAgents, tickElevator,
  makePackets, tickPackets,
});
