/* Ported from apps/web/src/features/home/{signals,accounts,people,lists}/…-query.ts
 * and pages/UserHome|UserLists — the mock data layer.
 *
 * T.Data replaces every Apollo useQuery/useMutation the ported pages call.
 * Hooks return { data, previousData, loading:false, error: undefined,
 * networkStatus: 7, refetch(){}, startPolling(){}, stopPolling(){} } with the
 * SAME GraphQL result keys the real queries resolve to:
 *   events, eventsCount, newsfeedFilterStats, account, accounts, myAccounts,
 *   accountCounts, accountsCount, people, peopleCount, selectablePeopleCount,
 *   peopleFilterStats, person, lists, discoveringAccountsCount,
 *   personOutreachHistory, accountOutreachHistory, listContactCoverage,
 *   listExportPeople, resolveAccountByDomain, resolveAccountByLinkedin,
 *   searchCoresignalByName, searchCoresignalPeopleByName, myTenant.
 *
 * All companies are REAL well-known domains (so LogoAvatar favicons resolve);
 * every person is FICTIONAL. Lists persist to localStorage `t-skeleton-lists`.
 */
(() => {
  const T = window.T;
  const { useMemo, useSyncExternalStore } = React;

  /* ================= time helpers ================= */
  const DAY = 24 * 60 * 60 * 1000;
  /** ISO timestamp `d` days ago (mid-morning, so "yesterday evening" edge
   *  cases in dayBucket never flip a row across buckets on reload). */
  const ago = (d, h = 10) => new Date(Date.now() - d * DAY - h * 3600000).toISOString();

  const logo = (domain) => `https://www.google.com/s2/favicons?sz=128&domain=${domain}`;

  /* ================= accounts (25) ================= */
  // AccountRow: id,name,url,logoUrl,oneLiner,employeeCount,annualRevenueFrom,
  // annualRevenueTo,ownership,hqLocation,timingScore,createdAt (+ researchInfo
  // for the drawer). ownership: 'public' | 'private' (TypeChip lowercases).
  const A = (id, name, domain, oneLiner, employeeCount, revFrom, revTo, ownership, hqLocation, timingScore, createdDays, researchInfo) => ({
    id,
    name,
    url: `https://${domain}`,
    logoUrl: logo(domain),
    oneLiner,
    employeeCount,
    annualRevenueFrom: revFrom,
    annualRevenueTo: revTo,
    ownership,
    hqLocation,
    timingScore,
    createdAt: ago(createdDays),
    researchInfo: researchInfo || null,
  });

  const M = 1000000;
  const accounts = [
    A('a-stripe', 'Stripe', 'stripe.com', 'Payments infrastructure for the internet.', 8500, 14000 * M, 16000 * M, 'private', 'San Francisco, CA', 91, 210,
      'Stripe is doubling down on AI-assisted payments tooling. Recent launches (foundation model for payments, usage-based billing revamp) point to platform expansion beyond core processing. Buying committee for security tooling sits under the CISO org.'),
    A('a-figma', 'Figma', 'figma.com', 'Collaborative interface design platform.', 1600, 700 * M, 900 * M, 'public', 'San Francisco, CA', 86, 210),
    A('a-notion', 'Notion', 'notion.so', 'Connected workspace for docs, wikis, and projects.', 800, 350 * M, 500 * M, 'private', 'San Francisco, CA', 64, 190),
    A('a-vercel', 'Vercel', 'vercel.com', 'Frontend cloud: build, preview, and ship the web.', 650, 150 * M, 250 * M, 'private', 'San Francisco, CA', 83, 180),
    A('a-datadog', 'Datadog', 'datadoghq.com', 'Observability and security for cloud applications.', 5800, 2500 * M, 2700 * M, 'public', 'New York, NY', 88, 200,
      'Datadog is consolidating SIEM and observability spend. Their SOC automation hiring wave signals in-house detection engineering growth — a strong wedge for workflow tooling.'),
    A('a-snowflake', 'Snowflake', 'snowflake.com', 'AI data cloud for analytics and applications.', 7300, 3400 * M, 3600 * M, 'public', 'Bozeman, MT', 79, 200),
    A('a-hashicorp', 'HashiCorp', 'hashicorp.com', 'Infrastructure automation for multi-cloud.', 2300, 650 * M, 750 * M, 'public', 'San Francisco, CA', 42, 170),
    A('a-cloudflare', 'Cloudflare', 'cloudflare.com', 'Connectivity cloud: security, performance, developer platform.', 3900, 1600 * M, 1800 * M, 'public', 'San Francisco, CA', 72, 160),
    A('a-gitlab', 'GitLab', 'gitlab.com', 'DevSecOps platform delivered as a single application.', 2100, 750 * M, 850 * M, 'public', 'Remote', 58, 150),
    A('a-atlassian', 'Atlassian', 'atlassian.com', 'Team collaboration and developer tools.', 12000, 4300 * M, 4600 * M, 'public', 'Sydney, Australia', 35, 150),
    A('a-mongodb', 'MongoDB', 'mongodb.com', 'Developer data platform built on the document database.', 5500, 1900 * M, 2100 * M, 'public', 'New York, NY', 66, 140),
    A('a-twilio', 'Twilio', 'twilio.com', 'Customer engagement APIs: messaging, voice, email.', 5900, 4100 * M, 4300 * M, 'public', 'San Francisco, CA', 28, 140),
    A('a-shopify', 'Shopify', 'shopify.com', 'Commerce platform powering millions of merchants.', 11600, 7000 * M, 7400 * M, 'public', 'Ottawa, Canada', 61, 130),
    A('a-hubspot', 'HubSpot', 'hubspot.com', 'Customer platform for marketing, sales, and service.', 7600, 2300 * M, 2500 * M, 'public', 'Cambridge, MA', 44, 120),
    A('a-zoom', 'Zoom', 'zoom.us', 'AI-first work platform for video, phone, and chat.', 7400, 4500 * M, 4700 * M, 'public', 'San Jose, CA', 22, 120),
    A('a-slack', 'Slack', 'slack.com', 'Work OS: channels, huddles, and workflow automation.', 3000, 1700 * M, 1900 * M, 'private', 'San Francisco, CA', 31, 110),
    A('a-dropbox', 'Dropbox', 'dropbox.com', 'Content collaboration and intelligent file storage.', 2700, 2500 * M, 2600 * M, 'public', 'San Francisco, CA', 18, 110),
    A('a-asana', 'Asana', 'asana.com', 'Work management for cross-functional teams.', 1800, 650 * M, 720 * M, 'public', 'San Francisco, CA', 26, 100),
    A('a-airtable', 'Airtable', 'airtable.com', 'No-code app platform on a collaborative database.', 750, 250 * M, 350 * M, 'private', 'San Francisco, CA', 39, 95),
    A('a-intercom', 'Intercom', 'intercom.com', 'AI-first customer service platform.', 1100, 300 * M, 400 * M, 'private', 'San Francisco, CA', 53, 90),
    A('a-segment', 'Segment', 'segment.com', 'Customer data platform (Twilio Segment).', 600, 200 * M, 300 * M, 'private', 'San Francisco, CA', 12, 85),
    A('a-plaid', 'Plaid', 'plaid.com', 'Financial data network connecting apps to bank accounts.', 1200, 400 * M, 500 * M, 'private', 'San Francisco, CA', 76, 80),
    A('a-rippling', 'Rippling', 'rippling.com', 'Workforce platform: HR, IT, and finance in one system.', 3200, 550 * M, 700 * M, 'private', 'San Francisco, CA', 69, 70),
    A('a-ramp', 'Ramp', 'ramp.com', 'Finance operations platform: cards, expenses, procurement.', 1100, 300 * M, 450 * M, 'private', 'New York, NY', 81, 60),
    A('a-brex', 'Brex', 'brex.com', 'AI-powered spend platform for startups and enterprises.', 1300, 400 * M, 500 * M, 'private', 'San Francisco, CA', 47, 60),
  ];
  const acctById = (id) => accounts.find((a) => a.id === id) || null;
  const acctLite = (id) => {
    const a = acctById(id);
    return a ? { id: a.id, name: a.name, url: a.url, logoUrl: a.logoUrl } : null;
  };

  /* ================= people (61, all fictional) ================= */
  // HomePersonRow + drawer extras. enrichmentState machine:
  // not_requested | in_progress | found | not_found | error | bounced.
  const P = (id, fullName, title, acctId, o = {}) => {
    const acct = acctById(acctId);
    const domain = acct ? acct.url.replace('https://', '') : null;
    const email =
      o.email !== undefined
        ? o.email
        : o.state === 'found' && domain
          ? `${fullName.toLowerCase().replace(/[^a-z ]/g, '').trim().replace(/ +/g, '.')}@${domain}`
          : null;
    return {
      id,
      fullName,
      title,
      profileImageUrl: null, // fictional people: initials avatar
      accountId: acctId,
      email,
      deliverableEmail: email,
      deliverablePhone: o.phone || null,
      enrichmentState: o.state || (email ? 'found' : 'not_requested'),
      enrichmentRetryAvailableAt: o.retryAt || null,
      phoneEnrichmentState: o.phone ? 'found' : 'not_requested',
      phoneEnrichmentRetryAvailableAt: null,
      linkedinUsername: o.li || fullName.toLowerCase().replace(/[^a-z ]/g, '').trim().replace(/ +/g, '-'),
      lastContactedAt: o.contactedDays != null ? ago(o.contactedDays) : null,
      lastContactedByName: o.contactedDays != null ? (o.byMe ? 'You' : o.by || 'Alex Rivera') : null,
      contactedByMe: o.contactedDays != null ? !!o.byMe : false,
      createdAt: ago(o.createdDays != null ? o.createdDays : 45),
      account: acctLite(acctId),
    };
  };

  const people = [
    // Stripe
    P('p01', 'Marisol Vega', 'Chief Information Security Officer', 'a-stripe', { state: 'found', contactedDays: 6, byMe: true, createdDays: 120, phone: '+1 415 555 0114' }),
    P('p02', 'Devon Okafor', 'VP Engineering, Payments Intelligence', 'a-stripe', { state: 'found', createdDays: 118 }),
    P('p03', 'Ingrid Halvorsen', 'Head of Detection & Response', 'a-stripe', { state: 'in_progress', createdDays: 5 }),
    P('p04', 'Tomas Rueda', 'Staff Security Engineer', 'a-stripe', { state: 'not_requested', createdDays: 5 }),
    // Figma
    P('p05', 'Priya Raghavan', 'VP of Security', 'a-figma', { state: 'found', contactedDays: 12, by: 'Sam Porter', createdDays: 110 }),
    P('p06', 'Caleb Whitfield', 'Security Engineering Manager', 'a-figma', { state: 'found', createdDays: 110 }),
    P('p07', 'Noa Ben-Ami', 'Director of IT', 'a-figma', { state: 'not_found', createdDays: 90, retryAt: ago(-12) }),
    P('p08', 'Rosa Camacho', 'Recruiter, Security & Platform', 'a-figma', { state: 'not_requested', createdDays: 3 }),
    // Notion
    P('p09', 'Anders Lindqvist', 'Head of Infrastructure', 'a-notion', { state: 'found', createdDays: 100 }),
    P('p10', 'Yuki Tanabe', 'Engineering Manager, Growth', 'a-notion', { state: 'not_requested', createdDays: 95 }),
    P('p11', 'Farah El-Sayed', 'Chief Technology Officer', 'a-notion', { state: 'found', contactedDays: 30, by: 'Dana Cohen', createdDays: 95 }),
    // Vercel
    P('p12', 'Lucia Ferraro', 'VP Finance', 'a-vercel', { state: 'found', createdDays: 88 }),
    P('p13', 'Malik Thompson', 'Head of Platform Security', 'a-vercel', { state: 'in_progress', createdDays: 2 }),
    P('p14', 'Greta Somogyi', 'Director of Revenue Operations', 'a-vercel', { state: 'found', contactedDays: 9, byMe: true, createdDays: 85 }),
    // Datadog
    P('p15', 'Owen Gallagher', 'Director, Security Platform', 'a-datadog', { state: 'found', contactedDays: 4, by: 'Sam Porter', createdDays: 130, phone: '+1 212 555 0192' }),
    P('p16', 'Sofia Marchetti', 'SOC Automation Lead', 'a-datadog', { state: 'found', createdDays: 8 }),
    P('p17', 'Jae-won Park', 'VP Engineering, Cloud SIEM', 'a-datadog', { state: 'found', createdDays: 130 }),
    P('p18', 'Helena Vasquez', 'Talent Partner, Security Engineering', 'a-datadog', { state: 'not_requested', createdDays: 7 }),
    // Snowflake
    P('p19', 'Rafael Ortiz', 'Chief Information Security Officer', 'a-snowflake', { state: 'found', createdDays: 14 }),
    P('p20', 'Camille Fournier-Bard', 'VP Data Platform', 'a-snowflake', { state: 'found', contactedDays: 21, by: 'Dana Cohen', createdDays: 125 }),
    P('p21', 'Bhavin Mehta', 'Senior Director, Governance & Trust', 'a-snowflake', { state: 'bounced', createdDays: 120 }),
    P('p22', 'Astrid Nyström', 'Security Operations Manager', 'a-snowflake', { state: 'not_requested', createdDays: 60 }),
    // HashiCorp
    P('p23', 'Miles Abernathy', 'Director of Product Security', 'a-hashicorp', { state: 'found', createdDays: 100 }),
    P('p24', 'Zeynep Karahan', 'Platform Engineering Lead', 'a-hashicorp', { state: 'not_requested', createdDays: 100 }),
    // Cloudflare
    P('p25', 'Imani Delacroix', 'VP Emerging Products', 'a-cloudflare', { state: 'found', contactedDays: 17, byMe: true, createdDays: 105 }),
    P('p26', 'Viktor Palenik', 'Head of Zero Trust Engineering', 'a-cloudflare', { state: 'found', createdDays: 105 }),
    P('p27', 'June Nakagawa', 'Director, Developer Platform', 'a-cloudflare', { state: 'error', createdDays: 95 }),
    // GitLab
    P('p28', 'Santiago Belmonte', 'VP of Infrastructure', 'a-gitlab', { state: 'found', createdDays: 92 }),
    P('p29', 'Aoife Brennan', 'Principal Engineer, CI/CD', 'a-gitlab', { state: 'not_found', createdDays: 92 }),
    P('p30', 'Marcus Ellingboe', 'Chief Information Security Officer', 'a-gitlab', { state: 'found', contactedDays: 40, by: 'Sam Porter', createdDays: 92 }),
    // Atlassian
    P('p31', 'Talia Rosenfeld', 'Head of Enterprise Trust', 'a-atlassian', { state: 'found', createdDays: 85 }),
    P('p32', 'Dmitri Kovalenko', 'Engineering Manager, Cloud Migrations', 'a-atlassian', { state: 'not_requested', createdDays: 85 }),
    // MongoDB
    P('p33', 'Renata Silveira', 'VP Developer Experience', 'a-mongodb', { state: 'found', createdDays: 78 }),
    P('p34', 'Kofi Ansah-Mensah', 'Director of Search & AI', 'a-mongodb', { state: 'found', contactedDays: 2, byMe: true, createdDays: 12 }),
    P('p35', 'Elin Sandvik', 'Staff Product Manager, Atlas', 'a-mongodb', { state: 'not_requested', createdDays: 78 }),
    // Twilio
    P('p36', 'Gabriel Fontaine', 'Head of Messaging Infrastructure', 'a-twilio', { state: 'found', createdDays: 74 }),
    P('p37', 'Leilani Kahale', 'Director, Trust & Compliance', 'a-twilio', { state: 'not_requested', createdDays: 74 }),
    // Shopify
    P('p38', 'Oksana Melnyk', 'VP Checkout & Payments', 'a-shopify', { state: 'found', createdDays: 66 }),
    P('p39', 'Jasper Vandenberg', 'Senior Director, Merchant Security', 'a-shopify', { state: 'found', contactedDays: 26, by: 'Dana Cohen', createdDays: 66 }),
    P('p40', 'Amara Diallo', 'Engineering Lead, Core Commerce', 'a-shopify', { state: 'in_progress', createdDays: 6 }),
    // HubSpot
    P('p41', 'Niall Cudmore', 'VP Revenue Operations', 'a-hubspot', { state: 'found', createdDays: 58 }),
    P('p42', 'Beatriz Quintana', 'Director of Marketing Analytics', 'a-hubspot', { state: 'not_requested', createdDays: 58 }),
    // Zoom
    P('p43', 'Hana Watanabe-Cole', 'Head of AI Platform', 'a-zoom', { state: 'found', createdDays: 54 }),
    P('p44', 'Emeka Obi', 'Director, Enterprise Security', 'a-zoom', { state: 'not_requested', createdDays: 54 }),
    // Slack
    P('p45', 'Vera Lindholm', 'VP Platform & Automation', 'a-slack', { state: 'found', createdDays: 50 }),
    P('p46', 'Rohan Chatterjee', 'Engineering Manager, Workflow Builder', 'a-slack', { state: 'not_requested', createdDays: 50 }),
    // Dropbox
    P('p47', 'Célia Marchand', 'Head of IT Operations', 'a-dropbox', { state: 'found', createdDays: 47 }),
    P('p48', 'Teodor Iliev', 'Staff Security Engineer', 'a-dropbox', { state: 'not_requested', createdDays: 47 }),
    // Asana
    P('p49', 'Maya Krishnamurthy', 'VP Business Technology', 'a-asana', { state: 'found', createdDays: 44 }),
    P('p50', 'Lars Eikeland', 'Director of Data Engineering', 'a-asana', { state: 'not_found', createdDays: 44 }),
    // Airtable
    P('p51', 'Poppy Ashworth', 'Head of Growth Engineering', 'a-airtable', { state: 'found', createdDays: 40 }),
    P('p52', 'Idris Bakare', 'Platform Operations Lead', 'a-airtable', { state: 'not_requested', createdDays: 40 }),
    // Intercom
    P('p53', 'Sinead Gallavan', 'VP Customer Engineering', 'a-intercom', { state: 'found', contactedDays: 33, by: 'Alex Rivera', createdDays: 38 }),
    P('p54', 'Mateus Carvalho', 'Director of AI Support', 'a-intercom', { state: 'not_requested', createdDays: 38 }),
    // Segment
    P('p55', 'Wren Delgado', 'Head of Data Infrastructure', 'a-segment', { state: 'found', createdDays: 35 }),
    // Plaid
    P('p56', 'Katarzyna Wozniak', 'VP Engineering', 'a-plaid', { state: 'found', createdDays: 10 }),
    P('p57', 'Desmond Achebe', 'Head of Risk & Identity', 'a-plaid', { state: 'found', contactedDays: 15, byMe: true, createdDays: 30, phone: '+1 628 555 0177' }),
    P('p58', 'Milena Petrova', 'Security Compliance Manager', 'a-plaid', { state: 'not_requested', createdDays: 30 }),
    // Rippling
    P('p59', 'Callum Forsythe', 'VP Go-to-Market Systems', 'a-rippling', { state: 'found', createdDays: 24 }),
    P('p60', 'Anouk Vermeulen', 'Head of Sales Engineering', 'a-rippling', { state: 'in_progress', createdDays: 4 }),
    // Ramp
    P('p61', 'Ezra Blumstein', 'Chief Financial Officer', 'a-ramp', { state: 'found', contactedDays: 1, byMe: true, createdDays: 22 }),
    P('p62', 'Selin Aydogan', 'VP Platform Engineering', 'a-ramp', { state: 'found', createdDays: 22 }),
    P('p63', 'Jonas Weidemann', 'Director of Procurement Product', 'a-ramp', { state: 'not_requested', createdDays: 20 }),
    // Brex
    P('p64', 'Tessa Okonjo', 'Head of Financial Partnerships', 'a-brex', { state: 'found', createdDays: 18 }),
    P('p65', 'Ruben Castellanos', 'Engineering Manager, Risk', 'a-brex', { state: 'not_requested', createdDays: 18 }),
  ];
  const personById = (id) => people.find((p) => p.id === id) || null;

  /* ================= signals / events (12) ================= */
  // HomeSignalEvent. data carries the fields the table/drawer read:
  // event_summary, why_it_matters, matched_signals[{signal_key,confidence,
  // evidence}], offerings[{name,offering}]. signalType: news | jobs | job_change.
  const EV = (id, acctId, signalType, title, days, o) => ({
    id,
    title,
    eventDate: ago(days, o.h || 9),
    url: o.url || null,
    publication: o.publication || null,
    accountId: acctId,
    signalType,
    data: {
      event_summary: o.summary || null,
      why_it_matters: o.why || null,
      matched_signals: o.key
        ? [{ signal_key: o.key, confidence: o.confidence || 'high', evidence: o.evidence || '' }]
        : [],
      offerings: o.fit ? [{ name: o.fit[0], offering: o.fit[1] }] : [],
    },
    account: acctLite(acctId),
    people: (o.people || []).map(([pid, reasoning], i) => {
      const p = personById(pid);
      return {
        id: `ep-${id}-${i}`,
        reasoning,
        person: p
          ? { id: p.id, fullName: p.fullName, title: p.title, profileImageUrl: p.profileImageUrl }
          : null,
      };
    }),
  });

  const events = [
    EV('ev01', 'a-stripe', 'news', 'Stripe unveils payments foundation model at Sessions 2026', 0, {
      url: 'https://stripe.com/newsroom', publication: 'TechCrunch',
      key: 'ai-product-launch', confidence: 'high',
      evidence: 'Announced a transformer model trained on tens of billions of transactions, plus an AI agent toolkit for billing operations.',
      summary: 'Stripe launched an AI foundation model for payments and an agent toolkit for billing teams.',
      why: 'A net-new AI platform means new data pipelines and new attack surface — security and infra leaders will be budgeting for tooling this quarter.',
      fit: ['Detection Coverage Review', 'Position the detection coverage assessment for teams standing up new AI data pipelines.'],
      people: [
        ['p01', 'Owns security review for every new Stripe platform launch.'],
        ['p02', 'Leads the payments-intelligence org shipping the foundation model.'],
      ],
    }),
    EV('ev02', 'a-ramp', 'news', 'Ramp raises $300M Series D-2 at $13B valuation', 1, {
      url: 'https://ramp.com/blog', publication: 'Bloomberg',
      key: 'growth-funding', confidence: 'high',
      evidence: 'Round led by existing investors; proceeds earmarked for agentic finance products and European expansion.',
      summary: 'Ramp closed $300M in new funding to accelerate agentic finance products and expand into Europe.',
      why: 'Fresh capital plus a stated automation roadmap is the classic window for platform and vendor consolidation conversations.',
      fit: ['Finance Automation Suite', 'Lead with the procurement-workflow integration Ramp evaluated last cycle.'],
      people: [
        ['p61', 'Announced the round and owns the deployment of proceeds.'],
        ['p62', 'Runs the platform org that will spend the infrastructure budget.'],
      ],
    }),
    EV('ev03', 'a-figma', 'jobs', 'Security Engineer, Detection & Response', 2, {
      url: 'https://www.figma.com/careers/', publication: 'Figma Careers',
      key: 'security-hiring', confidence: 'high',
      evidence: 'Third detection-and-response opening in six weeks; JD references building out a 24/7 internal SOC.',
      summary: 'Figma posted a Detection & Response engineering role, its third security opening in six weeks.',
      why: 'A hiring wave for an internal SOC means new tooling decisions before headcount lands — the evaluation window is now.',
      fit: ['SOC Automation Platform', 'Teams building a first SOC buy automation before they finish hiring.'],
      people: [
        ['p05', 'Owns the security org this role reports into.'],
        ['p06', 'Hiring manager on the posted requisition.'],
        ['p08', 'Sourcing for the security engineering pipeline.'],
      ],
    }),
    EV('ev04', 'a-snowflake', 'job_change', 'Rafael Ortiz joins Snowflake as CISO', 4, {
      url: 'https://www.linkedin.com/', publication: 'LinkedIn',
      key: 'security-leadership-change', confidence: 'high',
      evidence: 'Previously CISO at a Fortune 100 bank; announced his move in a public post citing "rebuilding trust tooling from first principles".',
      summary: 'Snowflake hired Rafael Ortiz as CISO, replacing an internal interim leader.',
      why: 'New security leaders re-evaluate the vendor stack in their first 90 days — this is the widest buying window this account will have all year.',
      fit: ['Security Posture Assessment', 'A new-CISO audit is the standard first-90-days entry point.'],
      people: [
        ['p19', 'The newly appointed CISO driving the re-evaluation.'],
        ['p21', 'Runs governance & trust; will operationalize the new roadmap.'],
      ],
    }),
    EV('ev05', 'a-datadog', 'jobs', 'SOC Automation Engineer (Cloud SIEM)', 5, {
      url: 'https://careers.datadoghq.com/', publication: 'Datadog Careers',
      key: 'soc-automation-hiring', confidence: 'medium',
      evidence: 'JD calls for SOAR playbook experience and "reducing mean-time-to-respond by 10x" across the Cloud SIEM product line.',
      summary: 'Datadog is hiring SOC automation engineers into its Cloud SIEM group.',
      why: 'They are productizing the exact workflow we sell into — partner or land the platform team before the internal build hardens.',
      fit: ['Workflow Integration SDK', 'Offer the SDK partnership track for SIEM vendors embedding response automation.'],
      people: [
        ['p16', 'Leads the SOC automation effort named in the posting.'],
        ['p17', 'VP over Cloud SIEM; owns build-vs-buy.'],
        ['p18', 'Recruiting for the automation team.'],
      ],
    }),
    EV('ev06', 'a-vercel', 'news', 'Vercel expands enterprise platform with EU data residency', 6, {
      url: 'https://vercel.com/blog', publication: 'Vercel Blog',
      key: 'platform-expansion', confidence: 'medium',
      evidence: 'Announced Frankfurt region GA plus an enterprise compliance bundle (SOC 2 + EU residency + audit logs).',
      summary: 'Vercel shipped EU data residency and a compliance bundle for enterprise customers.',
      why: 'Compliance-led expansion pulls security review forward — their platform security team is now the economic buyer for audit tooling.',
      fit: ['Compliance Evidence Automation', 'Residency launches multiply audit scope; automate the evidence collection.'],
      people: [
        ['p13', 'Owns platform security for the compliance bundle.'],
        ['p12', 'Signs off on compliance program spend.'],
      ],
    }),
    EV('ev07', 'a-mongodb', 'news', 'MongoDB acquires vector-search startup Voyage AI team', 9, {
      url: 'https://www.mongodb.com/blog', publication: 'The Information',
      key: 'ai-search-expansion', confidence: 'high',
      evidence: 'Acqui-hire adds 40 engineers to Atlas Search; roadmap slide leaked showing semantic retrieval GA next quarter.',
      summary: 'MongoDB folded a vector-search team into Atlas to accelerate semantic retrieval.',
      why: 'Post-acquisition integration quarters are when data-platform teams standardize their pipeline tooling.',
      fit: ['Data Pipeline Observability', 'Integration quarters need pipeline-level observability — lead with the Atlas connector.'],
      people: [
        ['p34', 'Runs Search & AI — the org absorbing the acquired team.'],
        ['p33', 'Owns the developer-experience surface the launch ships through.'],
      ],
    }),
    EV('ev08', 'a-rippling', 'jobs', 'GTM Systems Architect (Salesforce + CPQ)', 12, {
      url: 'https://www.rippling.com/careers', publication: 'Rippling Careers',
      key: 'gtm-expansion-hiring', confidence: 'medium',
      evidence: 'Fourth GTM-systems opening this quarter; JD mentions re-platforming quote-to-cash ahead of a new product line.',
      summary: 'Rippling is hiring GTM systems architects to re-platform quote-to-cash.',
      why: 'A quote-to-cash re-platform is a once-every-three-years window to land revenue tooling.',
      fit: ['RevOps Data Layer', 'Re-platforming teams consolidate their GTM data layer first.'],
      people: [
        ['p59', 'Owns GTM systems and the re-platform program.'],
        ['p60', 'Field-facing stakeholder for the CPQ workflow.'],
      ],
    }),
    EV('ev09', 'a-cloudflare', 'news', 'Cloudflare launches Workers AI inference marketplace', 16, {
      url: 'https://blog.cloudflare.com', publication: 'Cloudflare Blog',
      key: 'platform-expansion', confidence: 'medium',
      evidence: 'Marketplace opens third-party model hosting on the edge network with usage-based revenue share.',
      summary: 'Cloudflare opened an inference marketplace on Workers AI with revenue share for model providers.',
      why: 'Marketplace launches create partner-integration budgets and new security review queues on the same day.',
      fit: ['Edge Integration Review', 'Position the integration security review for marketplace onboarding.'],
      people: [
        ['p25', 'VP over emerging products including the marketplace.'],
        ['p27', 'Runs the developer platform the marketplace ships on.'],
      ],
    }),
    EV('ev10', 'a-plaid', 'job_change', 'Katarzyna Wozniak promoted to VP Engineering at Plaid', 21, {
      url: 'https://www.linkedin.com/', publication: 'LinkedIn',
      key: 'new-engineering-leader', confidence: 'medium',
      evidence: 'Internal promotion after leading the identity-verification rebuild; now owns all product engineering.',
      summary: 'Plaid promoted Katarzyna Wozniak to VP Engineering over all product engineering.',
      why: 'Newly promoted leaders consolidate tooling early to put their stamp on the org.',
      fit: ['Engineering Metrics Platform', 'New VPs standardize metrics in their first quarter.'],
      people: [
        ['p56', 'The promoted VP now owning tooling decisions.'],
        ['p57', 'Peer stakeholder for risk & identity workflows.'],
      ],
    }),
    EV('ev11', 'a-shopify', 'news', 'Shopify consolidates checkout stack after record Black Friday', 34, {
      url: 'https://www.shopify.com/news', publication: 'Reuters',
      key: 'data-stack-consolidation', confidence: 'low',
      evidence: 'Engineering blog details migrating three checkout data pipelines onto one streaming platform.',
      summary: 'Shopify is consolidating checkout data pipelines onto a single streaming platform.',
      why: 'Consolidation programs retire duplicate vendors — get on the keep-list before the bake-off ends.',
      fit: ['Streaming Cost Governance', 'Consolidations need cost governance across the surviving platform.'],
      people: [
        ['p38', 'Owns checkout & payments engineering.'],
        ['p40', 'Leading the core-commerce migration workstream.'],
      ],
    }),
    EV('ev12', 'a-gitlab', 'jobs', 'Staff Security Engineer, Supply Chain', 55, {
      url: 'https://about.gitlab.com/jobs/', publication: 'GitLab Careers',
      key: 'security-hiring', confidence: 'medium',
      evidence: 'JD scopes SLSA attestation and artifact signing across the DevSecOps platform.',
      summary: 'GitLab opened a staff-level supply-chain security role.',
      why: 'Supply-chain investment at platform vendors cascades into new compliance requirements for their customers — and new integrations for us.',
      fit: ['Artifact Attestation Service', 'Lead with the SLSA-compliance integration for CI platforms.'],
      people: [
        ['p30', 'CISO sponsoring the supply-chain program.'],
        ['p29', 'Principal engineer on CI/CD hardening.'],
      ],
    }),
  ];

  /* ================= outreach history ================= */
  const outreach = [
    { id: 'o1', personId: 'p61', accountId: 'a-ramp', subject: 'Congrats on the Series D-2 — automation roadmap', sentAt: ago(1, 6), byName: 'You', byMe: true, toName: 'Ezra Blumstein' },
    { id: 'o2', personId: 'p34', accountId: 'a-mongodb', subject: 'Atlas Search integration — pipeline observability', sentAt: ago(2, 5), byName: 'You', byMe: true, toName: 'Kofi Ansah-Mensah' },
    { id: 'o3', personId: 'p15', accountId: 'a-datadog', subject: 'SOC automation benchmarks for Cloud SIEM teams', sentAt: ago(4, 3), byName: 'Sam Porter', byMe: false, toName: 'Owen Gallagher' },
    { id: 'o4', personId: 'p01', accountId: 'a-stripe', subject: 'Detection coverage for the new payments model', sentAt: ago(6, 7), byName: 'You', byMe: true, toName: 'Marisol Vega' },
    { id: 'o5', personId: 'p14', accountId: 'a-vercel', subject: 'RevOps data layer — follow-up from the demo', sentAt: ago(9, 4), byName: 'You', byMe: true, toName: 'Greta Somogyi' },
    { id: 'o6', personId: 'p05', accountId: 'a-figma', subject: 'Building a first SOC — automation playbook', sentAt: ago(12, 5), byName: 'Sam Porter', byMe: false, toName: 'Priya Raghavan' },
    { id: 'o7', personId: 'p57', accountId: 'a-plaid', subject: 'Identity risk workflows — intro', sentAt: ago(15, 6), byName: 'You', byMe: true, toName: 'Desmond Achebe' },
    { id: 'o8', personId: 'p25', accountId: 'a-cloudflare', subject: 'Marketplace onboarding security review', sentAt: ago(17, 2), byName: 'You', byMe: true, toName: 'Imani Delacroix' },
    { id: 'o9', personId: 'p20', accountId: 'a-snowflake', subject: 'Data governance assessment offer', sentAt: ago(21, 4), byName: 'Dana Cohen', byMe: false, toName: 'Camille Fournier-Bard' },
    { id: 'o10', personId: 'p39', accountId: 'a-shopify', subject: 'Checkout consolidation — cost governance', sentAt: ago(26, 3), byName: 'Dana Cohen', byMe: false, toName: 'Jasper Vandenberg' },
  ];

  /* ================= "beyond your accounts / people" pools ================= */
  const coresignalCompanies = [
    { coresignalId: 71001, name: 'Linear', website: 'linear.app', linkedinUrl: 'https://www.linkedin.com/company/linearapp', employeeCount: 220, hqLocation: 'Remote', ownership: 'private', annualRevenue: 40 * M, description: 'Issue tracking and product planning for modern software teams.' },
    { coresignalId: 71002, name: 'Retool', website: 'retool.com', linkedinUrl: 'https://www.linkedin.com/company/retool', employeeCount: 450, hqLocation: 'San Francisco, CA', ownership: 'private', annualRevenue: 120 * M, description: 'Build internal tools fast with a low-code platform.' },
    { coresignalId: 71003, name: 'Supabase', website: 'supabase.com', linkedinUrl: 'https://www.linkedin.com/company/supabase', employeeCount: 130, hqLocation: 'Remote', ownership: 'private', annualRevenue: 30 * M, description: 'Open-source Firebase alternative built on Postgres.' },
    { coresignalId: 71004, name: 'PostHog', website: 'posthog.com', linkedinUrl: 'https://www.linkedin.com/company/posthog', employeeCount: 90, hqLocation: 'Remote', ownership: 'private', annualRevenue: 25 * M, description: 'Open-source product analytics, session replay, and feature flags.' },
    { coresignalId: 71005, name: 'Render', website: 'render.com', linkedinUrl: 'https://www.linkedin.com/company/renderco', employeeCount: 110, hqLocation: 'San Francisco, CA', ownership: 'private', annualRevenue: 20 * M, description: 'Cloud application hosting with zero devops.' },
    { coresignalId: 71006, name: 'Anthropic', website: 'anthropic.com', linkedinUrl: 'https://www.linkedin.com/company/anthropicresearch', employeeCount: 1500, hqLocation: 'San Francisco, CA', ownership: 'private', annualRevenue: 1000 * M, description: 'AI safety and research company building reliable AI systems.' },
  ];
  const coresignalPeople = [
    { employeeId: 82001, name: 'Iris Vandermeer', title: 'Head of Security', accountName: 'Linear', accountDomain: 'linear.app', linkedinUrl: 'https://www.linkedin.com/in/iris-vandermeer', profilePictureUrl: null },
    { employeeId: 82002, name: 'Quentin Marsh', title: 'VP Engineering', accountName: 'Retool', accountDomain: 'retool.com', linkedinUrl: 'https://www.linkedin.com/in/quentin-marsh', profilePictureUrl: null },
    { employeeId: 82003, name: 'Livia Andrade', title: 'Director of Platform', accountName: 'Supabase', accountDomain: 'supabase.com', linkedinUrl: 'https://www.linkedin.com/in/livia-andrade', profilePictureUrl: null },
    { employeeId: 82004, name: 'Otto Reinhardt', title: 'Head of Infrastructure', accountName: 'PostHog', accountDomain: 'posthog.com', linkedinUrl: 'https://www.linkedin.com/in/otto-reinhardt', profilePictureUrl: null },
  ];

  /* ================= reactive store ================= */
  let version = 0;
  const listeners = new Set();
  const bump = () => {
    version++;
    listeners.forEach((fn) => fn());
  };
  const subscribe = (fn) => {
    listeners.add(fn);
    return () => listeners.delete(fn);
  };
  const useDataVersion = () => useSyncExternalStore(subscribe, () => version);
  const delay = (ms = 250) => new Promise((res) => setTimeout(res, ms));

  /* ================= lists store (localStorage) ================= */
  const LISTS_KEY = 't-skeleton-lists';
  let memberSeq = 1000;

  /** ListMemberInput → resolved ListMemberRow (display meta from the raw data). */
  const resolveMember = (input) => {
    const kind = input.kind || (input.personId ? 'person' : 'account');
    const p = input.personId ? personById(input.personId) : null;
    return {
      id: `lm-${++memberSeq}`,
      kind,
      via: input.via || 'manual',
      accountId: input.accountId || (p ? p.accountId : null),
      eventId: input.eventId || null,
      per: kind === 'account' ? (input.per != null ? input.per : 1) : null,
      excluded: !!input.excluded,
      account: kind === 'account' ? acctLite(input.accountId) : null,
      person: p
        ? {
            id: p.id,
            fullName: p.fullName,
            title: p.title,
            profileImageUrl: p.profileImageUrl,
            accountId: p.accountId,
            account: p.account,
          }
        : null,
    };
  };

  const seedLists = () => [
    {
      id: 'list-security-buyers',
      name: 'Security buyers',
      origin: 'saved',
      lastOutreachAt: ago(12, 5),
      createdAt: ago(30),
      updatedAt: ago(2),
      members: [
        { kind: 'account', accountId: 'a-figma', via: 'signal', per: 3, eventId: 'ev03' },
        { kind: 'account', accountId: 'a-datadog', via: 'account', per: 3 },
        { personId: 'p19', via: 'signal', accountId: 'a-snowflake', eventId: 'ev04' },
        { personId: 'p01', via: 'manual', accountId: 'a-stripe' },
      ].map(resolveMember),
    },
    {
      id: 'list-q3-outreach',
      name: 'Q3 outreach',
      origin: 'saved',
      lastOutreachAt: ago(1, 6),
      createdAt: ago(21),
      updatedAt: ago(1),
      members: [
        { personId: 'p61', via: 'people-table', accountId: 'a-ramp' },
        { personId: 'p34', via: 'people-table', accountId: 'a-mongodb' },
        { personId: 'p14', via: 'people-table', accountId: 'a-vercel' },
        { personId: 'p57', via: 'people-table', accountId: 'a-plaid' },
        { personId: 'p25', via: 'people-table', accountId: 'a-cloudflare' },
        { personId: 'p53', via: 'people-table', accountId: 'a-intercom' },
      ].map(resolveMember),
    },
    {
      id: 'list-fintech-accounts',
      name: 'Fintech accounts',
      origin: 'import',
      lastOutreachAt: null,
      createdAt: ago(14),
      updatedAt: ago(6),
      members: [
        { kind: 'account', accountId: 'a-stripe', via: 'import', per: 1 },
        { kind: 'account', accountId: 'a-plaid', via: 'import', per: 1 },
        { kind: 'account', accountId: 'a-ramp', via: 'import', per: 1 },
        { kind: 'account', accountId: 'a-brex', via: 'import', per: 1 },
        { personId: 'p21', via: 'account', excluded: true },
      ].map(resolveMember),
    },
  ];

  const normalizeList = (l) => ({
    ...l,
    memberCount: (l.members || []).length,
    members: l.members || [],
  });

  let lists;
  try {
    const raw = localStorage.getItem(LISTS_KEY);
    lists = raw ? JSON.parse(raw).map(normalizeList) : null;
  } catch (e) {
    lists = null;
  }
  if (!Array.isArray(lists)) {
    lists = seedLists().map(normalizeList);
    try {
      localStorage.setItem(LISTS_KEY, JSON.stringify(lists));
    } catch (e) {
      /* private mode: in-memory only */
    }
  }

  const persistLists = () => {
    try {
      localStorage.setItem(LISTS_KEY, JSON.stringify(lists));
    } catch (e) {
      /* ignore */
    }
    bump();
  };

  /** Newest-updated first — the order the server returns them. */
  const sortedLists = () => [...lists].sort((x, y) => (y.updatedAt || '').localeCompare(x.updatedAt || ''));

  let listSeq = 1;
  const createList = async (name, members, origin = 'saved') => {
    await delay();
    const row = normalizeList({
      id: `list-${Date.now().toString(36)}-${listSeq++}`,
      name,
      origin,
      lastOutreachAt: null,
      createdAt: new Date().toISOString(),
      updatedAt: new Date().toISOString(),
      members: (members || []).map(resolveMember),
    });
    lists.push(row);
    persistLists();
    return { id: row.id, name: row.name, origin: row.origin, memberCount: row.memberCount, updatedAt: row.updatedAt };
  };

  const updateList = async (id, input) => {
    await delay();
    const row = lists.find((l) => l.id === id);
    if (!row) return null;
    if (input.name !== undefined) row.name = input.name;
    if (input.members !== undefined) row.members = (input.members || []).map(resolveMember);
    row.memberCount = row.members.length;
    row.updatedAt = new Date().toISOString();
    persistLists();
    return { id: row.id, name: row.name, memberCount: row.memberCount, updatedAt: row.updatedAt };
  };

  const deleteList = async (id) => {
    await delay();
    lists = lists.filter((l) => l.id !== id);
    persistLists();
    return true;
  };

  /* ================= derived indexes ================= */
  const eventsForAccount = (accountId) => events.filter((e) => e.accountId === accountId);
  const peopleForAccount = (accountId) => people.filter((p) => p.accountId === accountId);
  const latestEventForPerson = (personId) => {
    let latest = null;
    for (const e of events) {
      if (e.people.some((ep) => ep.person && ep.person.id === personId)) {
        if (!latest || (e.eventDate || '') > latest) latest = e.eventDate;
      }
    }
    return latest;
  };
  const lastOutreachForAccount = (accountId) => {
    let latest = null;
    for (const o of outreach) {
      if (o.accountId === accountId && (!latest || o.sentAt > latest)) latest = o.sentAt;
    }
    return latest;
  };

  /* ================= filtering / sorting ================= */
  const matches = (q, ...fields) => {
    const needle = (q || '').trim().toLowerCase();
    if (!needle) return true;
    return fields.some((f) => typeof f === 'string' && f.toLowerCase().includes(needle));
  };
  const humanize = (key) =>
    (key || '')
      .split(/[-_\s]+/)
      .filter(Boolean)
      .map((t) => t.charAt(0).toUpperCase() + t.slice(1))
      .join(' ');
  const eventKeys = (e) =>
    Array.isArray(e.data && e.data.matched_signals)
      ? e.data.matched_signals.map((m) => m.signal_key).filter(Boolean)
      : [];

  const filterEvents = (vars = {}) => {
    let rows = events;
    if (vars.eventIds && vars.eventIds.length) {
      const set = new Set(vars.eventIds);
      rows = rows.filter((e) => set.has(e.id));
    }
    if (vars.accountIds && vars.accountIds.length) {
      const set = new Set(vars.accountIds);
      rows = rows.filter((e) => set.has(e.accountId));
    }
    if (vars.accountId) rows = rows.filter((e) => e.accountId === vars.accountId);
    if (vars.signalIds && vars.signalIds.length) {
      const set = new Set(vars.signalIds);
      rows = rows.filter((e) => eventKeys(e).some((k) => set.has(k)));
    }
    if (vars.search) {
      rows = rows.filter((e) =>
        matches(
          vars.search,
          e.title,
          e.account && e.account.name,
          e.publication,
          e.data && e.data.event_summary,
          e.data && e.data.why_it_matters,
          ...eventKeys(e).map(humanize),
          ...e.people.map((ep) => ep.person && ep.person.fullName),
        ),
      );
    }
    rows = [...rows].sort((x, y) => (y.eventDate || '').localeCompare(x.eventDate || ''));
    return rows;
  };
  const pageOf = (rows, vars = {}) => {
    const skip = vars.skip || 0;
    const take = vars.take != null ? vars.take : rows.length;
    return rows.slice(skip, skip + take);
  };

  const emailRank = (p) => (p.email ? 2 : p.enrichmentState === 'in_progress' ? 1 : 0);
  const activityOf = (p) => {
    const candidates = [p.createdAt, p.lastContactedAt, latestEventForPerson(p.id)].filter(Boolean);
    return candidates.sort().pop() || '';
  };

  const filterPeople = (vars = {}) => {
    let rows = people;
    if (vars.personIds && vars.personIds.length) {
      const set = new Set(vars.personIds);
      rows = rows.filter((p) => set.has(p.id));
    }
    if (vars.scopeOr) {
      const pids = new Set(vars.scopeOr.personIds || []);
      const aids = new Set(vars.scopeOr.accountIds || []);
      const excl = new Set(vars.scopeOr.excludePersonIds || []);
      rows = rows.filter(
        (p) => (pids.has(p.id) || (p.accountId && aids.has(p.accountId))) && !excl.has(p.id),
      );
    }
    if (vars.accountIds && vars.accountIds.length) {
      const set = new Set(vars.accountIds);
      rows = rows.filter((p) => p.accountId && set.has(p.accountId));
    }
    if (vars.hasAccount) rows = rows.filter((p) => !!p.accountId);
    if (vars.search) {
      rows = rows.filter((p) =>
        matches(vars.search, p.fullName, p.title, p.email, p.account && p.account.name),
      );
    }
    const dir = vars.sortDir === 'asc' ? 1 : -1;
    const by = vars.sortBy || 'recentActivity';
    const cmpStr = (a, b) => (a || '').localeCompare(b || '');
    rows = [...rows].sort((x, y) => {
      switch (by) {
        case 'fullName':
          return dir * cmpStr(x.fullName, y.fullName);
        case 'title':
          return dir * cmpStr(x.title, y.title);
        case 'account':
          return dir * cmpStr(x.account && x.account.name, y.account && y.account.name);
        case 'enrichment':
          return dir * (emailRank(x) - emailRank(y));
        case 'lastOutreach':
          return dir * cmpStr(x.lastContactedAt, y.lastContactedAt);
        case 'recentActivity':
        default:
          return dir * cmpStr(activityOf(x), activityOf(y));
      }
    });
    return rows;
  };

  const filterAccounts = (vars = {}) => {
    let rows = accounts;
    if (vars.ids && vars.ids.length) {
      const set = new Set(vars.ids);
      rows = rows.filter((a) => set.has(a.id));
    }
    if (vars.search) {
      rows = rows.filter((a) => matches(vars.search, a.name, a.url, a.hqLocation, a.oneLiner));
    }
    const dir = vars.sortDir === 'asc' ? 1 : -1;
    const by = vars.sortBy || 'timingScore';
    const cmpStr = (a, b) => (a || '').localeCompare(b || '');
    const cmpNum = (a, b) => (a == null ? -1 : a) - (b == null ? -1 : b);
    rows = [...rows].sort((x, y) => {
      switch (by) {
        case 'name':
          return dir * cmpStr(x.name, y.name);
        case 'hq':
          return dir * cmpStr(x.hqLocation, y.hqLocation);
        case 'type':
          return dir * cmpStr(x.ownership, y.ownership);
        case 'employees':
          return dir * cmpNum(x.employeeCount, y.employeeCount);
        case 'revenue':
          return dir * cmpNum(x.annualRevenueFrom, y.annualRevenueFrom);
        case 'events':
          return dir * (eventsForAccount(x.id).length - eventsForAccount(y.id).length);
        case 'stakeholders':
          return dir * (peopleForAccount(x.id).length - peopleForAccount(y.id).length);
        case 'lastOutreach':
          return dir * cmpStr(lastOutreachForAccount(x.id), lastOutreachForAccount(y.id));
        case 'recentActivity':
          return dir * cmpStr(x.createdAt, y.createdAt);
        case 'timingScore':
        default:
          return dir * cmpNum(x.timingScore, y.timingScore);
      }
    });
    return rows;
  };

  /* ================= list resolution (coverage / export) ================= */
  /** People a list covers: pinned (non-excluded) + everyone at account members,
   *  minus exclusions. Coarser than the drawer's ranked resolution — used for
   *  the coverage/export dialogs, where the roster is the honest denominator. */
  const listPeopleOf = (list) => {
    if (!list) return [];
    const excl = new Set(list.members.filter((m) => m.excluded && m.person).map((m) => m.person.id));
    const out = new Map();
    for (const m of list.members) {
      if (m.kind === 'account' && m.accountId) {
        for (const p of peopleForAccount(m.accountId)) if (!excl.has(p.id)) out.set(p.id, p);
      } else if (m.person && !m.excluded) {
        const p = personById(m.person.id);
        if (p && !excl.has(p.id)) out.set(p.id, p);
      }
    }
    return [...out.values()];
  };

  /* ================= envelope + hook factory ================= */
  const envelope = (data) => ({
    data,
    previousData: data,
    loading: false,
    error: undefined,
    networkStatus: 7,
    refetch: () => Promise.resolve({ data }),
    startPolling: () => {},
    stopPolling: () => {},
  });

  /** useQuery-shaped hook: recomputes when vars change or the dataset mutates. */
  const makeHook = (compute) =>
    function useMockQuery(vars) {
      const v = useDataVersion();
      const key = JSON.stringify(vars == null ? null : vars);
      // eslint-disable-next-line react-hooks/exhaustive-deps
      const data = useMemo(() => compute(vars || {}), [key, v]);
      return useMemo(() => envelope(data), [data]);
    };

  /* ================= query hooks ================= */
  // GET_HOME_SIGNALS → { events }
  const useHomeSignals = makeHook((vars) => ({ events: pageOf(filterEvents(vars), vars) }));
  // GET_HOME_SIGNALS_BY_IDS → { events }
  const useHomeSignalsByIds = makeHook((vars) => ({ events: filterEvents({ eventIds: vars.eventIds || [] }) }));
  // GET_HOME_SIGNALS_COUNT → { eventsCount }
  const useHomeSignalsCount = makeHook((vars) => ({ eventsCount: filterEvents(vars).length }));
  // GET_HOME_SIGNAL_FACET → { newsfeedFilterStats: { signalCounts } }
  const useHomeSignalFacet = makeHook((vars) => {
    const rows = filterEvents({ accountIds: vars.accountIds });
    const byKey = new Map();
    for (const e of rows) {
      for (const k of eventKeys(e)) {
        const cur = byKey.get(k);
        if (cur) cur.count += 1;
        else byKey.set(k, { signalId: k, signalType: e.signalType, count: 1 });
      }
    }
    return { newsfeedFilterStats: { signalCounts: [...byKey.values()] } };
  });
  // GET_ACCOUNT_SIGNAL_COUNT → { eventsCount, account }
  const useAccountSignalCount = makeHook((vars) => {
    const a = acctById(vars.accountIdId || vars.accountId);
    return {
      eventsCount: eventsForAccount(vars.accountId).length,
      account: a ? { id: a.id, oneLiner: a.oneLiner } : null,
    };
  });
  // SignalDrawer GET_ACCOUNT_PEOPLE → { people } (id/fullName/title/photo)
  const useAccountPeople = makeHook((vars) => ({
    people: peopleForAccount(vars.accountId).map((p) => ({
      id: p.id,
      fullName: p.fullName,
      title: p.title,
      profileImageUrl: p.profileImageUrl,
    })),
  }));

  // GET_HOME_ACCOUNTS → { accounts } / GET_HOME_MY_ACCOUNTS → { myAccounts }
  const useHomeAccounts = makeHook((vars) => ({ accounts: pageOf(filterAccounts(vars), vars) }));
  const useHomeMyAccounts = makeHook((vars) => ({ myAccounts: pageOf(filterAccounts(vars), vars) }));
  // GET_HOME_ACCOUNT_COUNTS → { accountCounts }
  const useHomeAccountCounts = makeHook((vars) => ({
    accountCounts: (vars.ids || []).map((id) => ({
      accountId: id,
      eventCount: eventsForAccount(id).length,
      peopleCount: peopleForAccount(id).length,
      contactsDiscovering: false,
      signalsDiscovering: false,
      lastOutreachAt: lastOutreachForAccount(id),
    })),
  }));
  // GET_HOME_ACCOUNTS_TOTAL → { accountsCount }
  const useHomeAccountsTotal = makeHook((vars) => ({ accountsCount: filterAccounts(vars).length }));
  // GET_HOME_ACCOUNT_DRAWER → { account, events, people }
  const useHomeAccountDrawer = makeHook((vars) => {
    const a = acctById(vars.id);
    return {
      account: a
        ? {
            id: a.id,
            name: a.name,
            url: a.url,
            logoUrl: a.logoUrl,
            oneLiner: a.oneLiner,
            researchInfo: a.researchInfo,
            employeeCount: a.employeeCount,
            annualRevenueFrom: a.annualRevenueFrom,
            annualRevenueTo: a.annualRevenueTo,
            ownership: a.ownership,
            hqLocation: a.hqLocation,
            timingScore: a.timingScore,
          }
        : null,
      events: filterEvents({ accountId: vars.id }).slice(0, 30),
      people: peopleForAccount(vars.id).slice(0, 100).map((p) => ({
        id: p.id,
        fullName: p.fullName,
        title: p.title,
        profileImageUrl: p.profileImageUrl,
        accountId: p.accountId,
        email: p.email,
        enrichmentState: p.enrichmentState,
        lastContactedAt: p.lastContactedAt,
        lastContactedByName: p.lastContactedByName,
        contactedByMe: p.contactedByMe,
      })),
    };
  });

  // GET_HOME_PEOPLE → { people }
  const useHomePeople = makeHook((vars) => ({ people: pageOf(filterPeople(vars), vars) }));
  // GET_HOME_PEOPLE_TOTAL → { peopleCount, selectablePeopleCount }
  const useHomePeopleTotal = makeHook((vars) => ({
    peopleCount: filterPeople(vars).length,
    selectablePeopleCount: filterPeople({ ...vars, hasAccount: true }).length,
  }));
  // GET_HOME_PEOPLE_ACCOUNT_STATS → { peopleFilterStats: { accountCounts } }
  // NB: ignores the account filter itself (mirrors the server) so selecting an
  // account never collapses the dropdown.
  const useHomePeopleAccountStats = makeHook((vars) => {
    const rows = filterPeople({ scopeOr: vars.scopeOr, search: vars.search });
    const byAcct = new Map();
    for (const p of rows) {
      if (!p.accountId) continue;
      byAcct.set(p.accountId, (byAcct.get(p.accountId) || 0) + 1);
    }
    return {
      peopleFilterStats: {
        accountCounts: [...byAcct.entries()].map(([accountId, count]) => ({ accountId, count })),
      },
    };
  });
  // GET_HOME_PERSON_DRAWER → { person }
  const useHomePersonDrawer = makeHook((vars) => {
    const p = personById(vars.id);
    if (!p) return { person: null };
    const eventPeople = [];
    for (const e of events) {
      const ep = e.people.find((x) => x.person && x.person.id === p.id);
      if (ep) {
        eventPeople.push({
          reasoning: ep.reasoning,
          event: { id: e.id, title: e.title, eventDate: e.eventDate, url: e.url, data: e.data, status: 'completed' },
        });
      }
    }
    return {
      person: {
        id: p.id,
        fullName: p.fullName,
        title: p.title,
        profileImageUrl: p.profileImageUrl,
        linkedinUsername: p.linkedinUsername,
        accountId: p.accountId,
        email: p.email,
        deliverableEmail: p.deliverableEmail,
        enrichmentState: p.enrichmentState,
        enrichmentRetryAvailableAt: p.enrichmentRetryAvailableAt,
        deliverablePhone: p.deliverablePhone,
        phoneEnrichmentState: p.phoneEnrichmentState,
        phoneEnrichmentRetryAvailableAt: p.phoneEnrichmentRetryAvailableAt,
        lastContactedAt: p.lastContactedAt,
        account: p.account,
        eventPeople,
      },
    };
  });

  // UserHome GET_HOME_COUNTS → { eventsCount, accountsCount, peopleCount, discoveringAccountsCount }
  const useHomeCounts = makeHook((vars) => ({
    eventsCount: filterEvents({ accountIds: vars.accountIds }).length,
    accountsCount: filterAccounts({ ids: vars.ids }).length,
    peopleCount: filterPeople({ scopeOr: vars.peopleScopeOr }).length,
    discoveringAccountsCount: 0,
  }));

  // GET_LIST_PEOPLE → { people } (audience roster shape, perAccountTake 50)
  const useListPeople = makeHook((vars) => ({
    people: filterPeople({ accountIds: vars.accountIds || [] }).map((p) => ({
      id: p.id,
      fullName: p.fullName,
      title: p.title,
      profileImageUrl: p.profileImageUrl,
      email: p.email,
      enrichmentState: p.enrichmentState,
      lastContactedAt: p.lastContactedAt,
      lastContactedByName: p.lastContactedByName,
      contactedByMe: p.contactedByMe,
      accountId: p.accountId,
      account: p.account,
    })),
  }));
  // GET_LIST_EVENTS → { events } (lite ranking shape)
  const useListEvents = makeHook((vars) => ({
    events: filterEvents({ accountIds: vars.accountIds || [] })
      .slice(0, 120)
      .map((e) => ({
        id: e.id,
        accountId: e.accountId,
        eventDate: e.eventDate,
        account: e.account,
        people: e.people.map((ep) => ({ reasoning: ep.reasoning, person: ep.person ? { id: ep.person.id } : null })),
      })),
  }));

  // GET_PERSON_OUTREACH / GET_ACCOUNT_OUTREACH
  const usePersonOutreach = makeHook((vars) => ({
    personOutreachHistory: outreach
      .filter((o) => o.personId === vars.personId)
      .map(({ id, subject, sentAt, byName, byMe }) => ({ id, subject, sentAt, byName, byMe })),
  }));
  const useAccountOutreach = makeHook((vars) => ({
    accountOutreachHistory: outreach
      .filter((o) => o.accountId === vars.accountId)
      .slice(0, vars.take != null ? vars.take : outreach.length)
      .map(({ id, subject, sentAt, byName, byMe, toName }) => ({ id, subject, sentAt, byName, byMe, toName })),
  }));

  // LIST_CONTACT_COVERAGE / LIST_EXPORT_PEOPLE
  const useListContactCoverage = makeHook((vars) => {
    const roster = listPeopleOf(lists.find((l) => l.id === vars.listId));
    const withEmail = roster.filter((p) => !!p.email).length;
    const withPhone = roster.filter((p) => !!p.deliverablePhone).length;
    const enrichableEmail = roster.filter((p) => !p.email && p.enrichmentState !== 'not_found').length;
    const enrichablePhone = roster.filter((p) => !p.deliverablePhone).length;
    return {
      listContactCoverage: {
        totalPeople: roster.length,
        withEmail,
        withPhone,
        missingEmail: roster.length - withEmail,
        missingPhone: roster.length - withPhone,
        emailCreditsRemaining: 250,
        phoneCreditsRemaining: 100,
        enrichableEmail,
        enrichablePhone,
      },
    };
  });
  const useListExportPeople = makeHook((vars) => ({
    listExportPeople: listPeopleOf(lists.find((l) => l.id === vars.listId)).map((p) => ({
      personId: p.id,
      fullName: p.fullName,
      title: p.title,
      company: p.account ? p.account.name : null,
      email: p.email,
      phone: p.deliverablePhone,
      linkedinUrl: p.linkedinUsername ? `https://www.linkedin.com/in/${p.linkedinUsername}` : null,
    })),
  }));

  // "Beyond your accounts / people" lookups
  const useResolveAccountByDomain = makeHook((vars) => {
    const d = (vars.domain || '').toLowerCase();
    const hit = coresignalCompanies.find((c) => c.website === d);
    return { resolveAccountByDomain: hit ? { ...hit, linkedinUrl: undefined } : null };
  });
  const useResolveAccountByLinkedin = makeHook((vars) => {
    const h = (vars.handle || '').toLowerCase();
    const hit = coresignalCompanies.find((c) => (c.linkedinUrl || '').toLowerCase().endsWith(`/${h}`));
    return { resolveAccountByLinkedin: hit ? { ...hit, linkedinUrl: undefined } : null };
  });
  const useSearchCoresignal = makeHook((vars) => ({
    searchCoresignalByName: coresignalCompanies
      .filter((c) => matches(vars.query, c.name, c.website, c.description))
      .slice(0, vars.limit || 5),
  }));
  const useSearchCoresignalPeople = makeHook((vars) => ({
    searchCoresignalPeopleByName: coresignalPeople
      .filter((c) => matches(vars.query, c.name, c.title, c.accountName))
      .slice(0, vars.limit || 5),
  }));

  // GET_TENANT_SUBSCRIPTION → { myTenant } (generous: no plan gates in the mock)
  const useTenantSubscription = makeHook(() => ({
    myTenant: {
      id: 'tenant-1',
      plan: 'ENTERPRISE',
      subscription: {
        status: 'active',
        currentPeriodEnd: ago(-30),
        cancelAtPeriodEnd: false,
        quantity: 12,
        priceLookupKey: 'enterprise_annual',
        isLive: true,
      },
    },
  }));

  /* ================= lists hooks ================= */
  // 1:1 port of useLists() (features/home/lists/useLists.ts).
  const useLists = () => {
    const v = useDataVersion();
    // eslint-disable-next-line react-hooks/exhaustive-deps
    const rows = useMemo(() => sortedLists(), [v]);
    return {
      lists: rows,
      loading: false,
      refetch: () => Promise.resolve({ data: { lists: sortedLists() } }),
      createList,
      updateList,
      deleteList,
    };
  };
  // GET_LISTS as a raw query envelope → { lists }
  const useHomeLists = makeHook(() => ({ lists: sortedLists() }));
  // Detail lookup for the Lists page drawer / deep links.
  const useListDetail = (listId) => {
    const v = useDataVersion();
    // eslint-disable-next-line react-hooks/exhaustive-deps
    const list = useMemo(() => lists.find((l) => l.id === listId) || null, [listId, v]);
    return { list, ...envelope({ list }) };
  };
  // Port of useLinkedListName(linkedListId).
  const useLinkedListName = (linkedListId) => {
    const { lists: rows } = useLists();
    if (!linkedListId) return 'New list';
    const hit = rows.find((l) => l.id === linkedListId);
    return hit ? hit.name : 'New list';
  };

  /* ================= mutations ================= */
  const deleteAccount = async (id) => {
    await delay();
    const i = accounts.findIndex((a) => a.id === id);
    if (i >= 0) accounts.splice(i, 1);
    // Cascade like the server: the account's people, events, and list rows go.
    for (let j = people.length - 1; j >= 0; j--) if (people[j].accountId === id) people.splice(j, 1);
    for (let j = events.length - 1; j >= 0; j--) if (events[j].accountId === id) events.splice(j, 1);
    for (const l of lists) {
      l.members = l.members.filter(
        (m) => m.accountId !== id && !(m.person && m.person.accountId === id),
      );
      l.memberCount = l.members.length;
    }
    persistLists();
    return true;
  };

  const deletePerson = async (id) => {
    await delay();
    const i = people.findIndex((p) => p.id === id);
    if (i >= 0) people.splice(i, 1);
    for (const e of events) e.people = e.people.filter((ep) => !ep.person || ep.person.id !== id);
    for (const l of lists) {
      l.members = l.members.filter((m) => !m.person || m.person.id !== id);
      l.memberCount = l.members.length;
    }
    persistLists();
    return true;
  };

  /** Delete a signal row (the drawer's remove action). */
  const deleteEvent = async (id) => {
    await delay();
    const i = events.findIndex((e) => e.id === id);
    if (i >= 0) events.splice(i, 1);
    bump();
    return true;
  };

  let createdSeq = 1;
  // CREATE_ACCOUNT → { account, wasShortCircuited }
  const createAccount = async (input) => {
    await delay(400);
    const domain = (input.url || input.website || '').replace(/^https?:\/\//, '').replace(/^www\./, '').split('/')[0] || 'example.com';
    const row = A(
      `a-new-${createdSeq++}`,
      input.name || domain,
      domain,
      input.oneLiner || null,
      input.employeeCount != null ? input.employeeCount : null,
      input.annualRevenueFrom != null ? input.annualRevenueFrom : null,
      input.annualRevenueTo != null ? input.annualRevenueTo : null,
      input.ownership || null,
      input.hqLocation || null,
      null,
      0,
    );
    accounts.push(row);
    bump();
    return { account: row, wasShortCircuited: false };
  };

  // ENSURE_CONTACT_ACCOUNT → accountId (match-or-create)
  const ensureContactAccount = async (companyName, companyUrl) => {
    await delay();
    const domain = (companyUrl || '').replace(/^https?:\/\//, '').replace(/^www\./, '').split('/')[0];
    const hit = accounts.find(
      (a) =>
        a.name.toLowerCase() === (companyName || '').toLowerCase() ||
        (domain && a.url && a.url.includes(domain)),
    );
    if (hit) return hit.id;
    const res = await createAccount({ name: companyName, url: companyUrl });
    return res.account.id;
  };

  // CREATE_PERSON → person row
  const createPerson = async (input) => {
    await delay(400);
    const row = P(`p-new-${createdSeq++}`, input.fullName || input.name || 'New Contact', input.title || null, input.accountId || null, {
      email: input.email || null,
      li: input.linkedinUsername || undefined,
      createdDays: 0,
    });
    if (input.profileImageUrl) row.profileImageUrl = input.profileImageUrl;
    people.push(row);
    bump();
    return row;
  };

  // LOOKUP_PERSON_BY_LINKEDIN → person (idempotent: existing match surfaced)
  const lookupPersonByLinkedin = async (linkedinUrl) => {
    await delay(500);
    const slug = (linkedinUrl || '').split('/in/')[1]?.replace(/\/+$/, '') || '';
    const hit = people.find((p) => p.linkedinUsername === slug);
    if (hit) {
      return { id: hit.id, fullName: hit.fullName, title: hit.title, profileImageUrl: hit.profileImageUrl, accountId: hit.accountId, account: hit.account };
    }
    const name = humanize(slug || 'new contact');
    const row = await createPerson({ fullName: name, title: null, accountId: null, linkedinUsername: slug });
    return { id: row.id, fullName: row.fullName, title: row.title, profileImageUrl: row.profileImageUrl, accountId: row.accountId, account: row.account };
  };

  /** Simulated enrichment: in_progress now, found (with a minted email) later. */
  const requestEmailEnrichment = async (personId) => {
    const p = personById(personId);
    if (!p) return false;
    p.enrichmentState = 'in_progress';
    bump();
    setTimeout(() => {
      const still = personById(personId);
      if (!still || still.enrichmentState !== 'in_progress') return;
      const domain = still.account ? still.account.url.replace('https://', '') : 'example.com';
      still.email = `${still.fullName.toLowerCase().replace(/[^a-z ]/g, '').trim().replace(/ +/g, '.')}@${domain}`;
      still.deliverableEmail = still.email;
      still.enrichmentState = 'found';
      bump();
    }, 2500);
    return true;
  };
  const requestPhoneEnrichment = async (personId) => {
    const p = personById(personId);
    if (!p) return false;
    p.phoneEnrichmentState = 'in_progress';
    bump();
    setTimeout(() => {
      const still = personById(personId);
      if (!still || still.phoneEnrichmentState !== 'in_progress') return;
      still.deliverablePhone = `+1 415 555 0${100 + Math.floor(Math.random() * 900)}`.slice(0, 15);
      still.phoneEnrichmentState = 'found';
      bump();
    }, 2500);
    return true;
  };

  // SUBMIT_LIST_ENRICH_JOB → { id, kind, status, data }
  const submitListEnrichJob = async (input) => {
    await delay(400);
    const roster = listPeopleOf(lists.find((l) => l.id === (input && input.listId)));
    // Enrich the missing emails on the roster after a beat (simulated run).
    setTimeout(() => {
      for (const p of roster) if (!p.email && p.enrichmentState !== 'not_found') void requestEmailEnrichment(p.id);
    }, 800);
    return { id: `job-${Date.now().toString(36)}`, kind: 'list-enrich', status: 'queued', data: {} };
  };

  /* ================= export ================= */
  const raw = {
    accounts,
    people,
    events,
    outreach,
    coresignalCompanies,
    coresignalPeople,
    get lists() {
      return lists;
    },
    acctById,
    acctLite,
    personById,
    eventsForAccount,
    peopleForAccount,
    lastOutreachForAccount,
    listPeopleOf,
    filterEvents,
    filterPeople,
    filterAccounts,
    subscribe,
    bump,
  };

  Object.assign(T, {
    Data: {
      raw,
      // signals
      useHomeSignals,
      useHomeSignalsByIds,
      useHomeSignalsCount,
      useHomeSignalFacet,
      useAccountSignalCount,
      useAccountPeople,
      // accounts
      useHomeAccounts,
      useHomeMyAccounts,
      useHomeAccountCounts,
      useHomeAccountsTotal,
      useHomeAccountDrawer,
      // people
      useHomePeople,
      useHomePeopleTotal,
      useHomePeopleAccountStats,
      useHomePersonDrawer,
      // page chrome
      useHomeCounts,
      useTenantSubscription,
      // lists + audience roster
      useLists,
      useHomeLists,
      useListDetail,
      useLinkedListName,
      useListPeople,
      useListEvents,
      useListContactCoverage,
      useListExportPeople,
      // outreach
      usePersonOutreach,
      useAccountOutreach,
      // beyond-tenant lookups
      useResolveAccountByDomain,
      useResolveAccountByLinkedin,
      useSearchCoresignal,
      useSearchCoresignalPeople,
      // mutations
      createList,
      updateList,
      deleteList,
      deleteAccount,
      deletePerson,
      deleteEvent,
      deleteSignal: deleteEvent,
      createAccount,
      ensureContactAccount,
      createPerson,
      lookupPersonByLinkedin,
      requestEmailEnrichment,
      requestPhoneEnrichment,
      submitListEnrichJob,
      /* Optional contract consumed by ui.jsx shims (see NEEDS-ui.md). */
      getAccountsByIds: (ids) =>
        (ids || [])
          .map((id) => acctById(id))
          .filter(Boolean)
          .map((a) => ({
            id: a.id,
            name: a.name,
            domain: a.domain,
            logoUrl: a.logoUrl,
          })),
      resolveFindEmail: (personId) => {
        const p = personById(personId);
        if (!p) return { email: null, outcome: 'not_found' };
        const acct = p.accountId ? acctById(p.accountId) : null;
        const email =
          p.email ||
          `${(p.firstName || p.name || 'contact')
            .toLowerCase()
            .replace(/[^a-z]/g, '')}.${(p.lastName || '')
            .toLowerCase()
            .replace(/[^a-z]/g, '') || 'x'}@${acct ? acct.domain : 'example.com'}`;
        return { email, outcome: 'found' };
      },
      resolveFindPhone: (personId) => {
        const p = personById(personId);
        if (!p) return { phone: null, outcome: 'not_found' };
        const seed = String(personId)
          .split('')
          .reduce((h, c) => (h * 31 + c.charCodeAt(0)) >>> 0, 7);
        return {
          phone:
            p.phone ||
            `+1 (555) ${String(100 + (seed % 900))}-${String(1000 + (seed % 9000))}`,
          outcome: 'found',
        };
      },
      /* createImportedList consumed by the ui.jsx CSV-import shims. */
      createImportedList: ({ name, rows }) => {
        const members = (rows || []).map((r, i) => ({
          personId: `imported-${Date.now()}-${i}`,
          via: 'manual',
          accountId: null,
          imported: {
            name:
              r.name ||
              r.fullName ||
              `${r.firstName || ''} ${r.lastName || ''}`.trim(),
            email: r.email || null,
            title: r.title || null,
            company: r.company || null,
          },
        }));
        return createList(name || 'Imported list', members, 'imported');
      },
    },
  });

  /* Top-level alias — home.jsx and lists.jsx read T.useLists (the app's
   * features/home/lists/useLists.ts import path). */
  T.useLists = T.Data.useLists;
})();
