/* global React, ReactDOM, VoiceProvider, VoicePanel, useVoice */
const { useState, useEffect, useRef, useMemo, useCallback, createContext, useContext } = React;

// =========================================================================
// ROUTER
// =========================================================================
const RouteCtx = createContext(null);
function useRoute() { return useContext(RouteCtx); }

function RouteProvider({ children }) {
  const [route, setRoute] = useState(getCurrentRoute);
  useEffect(() => {
    const onRouteChange = () => setRoute(getCurrentRoute());
    window.addEventListener('hashchange', onRouteChange);
    window.addEventListener('popstate', onRouteChange);
    return () => {
      window.removeEventListener('hashchange', onRouteChange);
      window.removeEventListener('popstate', onRouteChange);
    };
  }, []);
  const nav = (to) => {
    const normalizedRoute = normalizeAppRoute(to);
    const canonicalUrl = getCanonicalFrontendUrl(normalizedRoute);
    if (canonicalUrl) {
      window.location.assign(canonicalUrl);
      return;
    }
    const currentChannel = getRouteAuthChannel(getCurrentRoute());
    const nextChannel = getRouteAuthChannel(normalizedRoute);
    if (currentChannel && nextChannel && currentChannel !== nextChannel) {
      window.location.assign(normalizedRoute);
      return;
    }
    window.history.pushState({}, '', normalizedRoute);
    setRoute(getCurrentRoute());
    const anchor = to.includes('#') ? to.split('#').pop() : '';
    if (anchor) {
      window.requestAnimationFrame(() => document.getElementById(anchor)?.scrollIntoView({ block: 'start' }));
    } else {
      window.scrollTo({ top: 0, behavior: 'instant' });
    }
  };
  return <RouteCtx.Provider value={{ route, nav }}>{children}</RouteCtx.Provider>;
}

function normalizeAppRoute(to) {
  const route = String(to || '/');
  if (route.startsWith('/#/')) return route.slice(2) || '/';
  if (route.startsWith('#/')) return route.slice(1) || '/';
  return route;
}

const CANONICAL_FRONTEND_HOSTS = {
  demand: 'idn.neevcloud.com',
  supply: 'capacity.neevcloud.com',
};

function getRouteParts(route) {
  const normalizedRoute = normalizeAppRoute(route || '/');
  try {
    const url = new URL(normalizedRoute, window.location.origin);
    return {
      pathname: url.pathname || '/',
      search: url.search || '',
      hash: url.hash || '',
    };
  } catch {
    const [pathAndQuery, hash = ''] = String(normalizedRoute).split('#');
    const [pathname = '/', search = ''] = pathAndQuery.split('?');
    return {
      pathname: pathname || '/',
      search: search ? `?${search}` : '',
      hash: hash ? `#${hash}` : '',
    };
  }
}

function getRouteHostChannel(route) {
  const { pathname } = getRouteParts(route);
  if (pathname === '/idn' || pathname === '/find-compute' || pathname === '/demand' || pathname.startsWith('/demand/')) return 'demand';
  if (pathname === '/submit-capacity' || pathname === '/submit-capacity/thank-you' || pathname === '/supply' || pathname.startsWith('/supply/')) return 'supply';
  return null;
}

function getCanonicalFrontendUrl(route) {
  const hostname = window.location.hostname.toLowerCase();
  const hosts = Object.values(CANONICAL_FRONTEND_HOSTS);
  if (!hosts.includes(hostname)) return '';

  const { pathname, search, hash } = getRouteParts(route);
  if (pathname === '/idn') return `https://${CANONICAL_FRONTEND_HOSTS.demand}/${search}${hash}`;
  const channel = getRouteHostChannel(pathname);
  if (!channel || CANONICAL_FRONTEND_HOSTS[channel] === hostname) return '';
  return `https://${CANONICAL_FRONTEND_HOSTS[channel]}${pathname}${search}${hash}`;
}

function redirectToCanonicalFrontendHost() {
  const canonicalUrl = getCanonicalFrontendUrl(`${window.location.pathname}${window.location.search}${window.location.hash}`);
  if (!canonicalUrl) return false;
  window.location.replace(canonicalUrl);
  return true;
}

// =========================================================================
// FORM STATE (autosave locally and to FastAPI)
// =========================================================================
const FormCtx = createContext(null);
function useForm() { return useContext(FormCtx); }

const STORAGE_KEY = 'neev-supply-intake-v5-level1';
const META_KEY = 'neev-supply-intake-meta-v5-level1';
const DEMAND_STORAGE_KEY = 'neev-demand-intake-v1';
const DEMAND_META_KEY = 'neev-demand-intake-meta-v1';
const ADMIN_TOKEN_KEY = 'neev-admin-access-token';
const ADMIN_SESSION_KEY = 'neev-admin-session';
const NEEVCLOUD_LOGO_SRC = 'assets/neevcloud-logo-dark.svg';
const ECOSYSTEM_LOGO_BASE = 'assets/logos';
const RUNTIME_CONFIG = window.NEEVCLOUD_RUNTIME_CONFIG || {};
const API_BASE = window.NEEVCLOUD_API_BASE || RUNTIME_CONFIG.NEEVCLOUD_API_BASE || (
  ['localhost', '127.0.0.1', '::1'].includes(window.location.hostname)
    ? 'http://127.0.0.1:8000'
    : ''
);
const CLERK_JS_URL = window.NEEVCLOUD_CLERK_JS_URL || RUNTIME_CONFIG.NEEVCLOUD_CLERK_JS_URL || '';
const SUPABASE_URL = window.NEEVCLOUD_SUPABASE_URL || RUNTIME_CONFIG.NEEVCLOUD_SUPABASE_URL || '';
const SUPABASE_ANON_KEY = window.NEEVCLOUD_SUPABASE_ANON_KEY || RUNTIME_CONFIG.NEEVCLOUD_SUPABASE_ANON_KEY || '';

const AUTH_CHANNELS = {
  demand: {
    label: 'Demand',
    portal: 'NeevCloud Demand Portal',
    publishableKey: window.NEEVCLOUD_DEMAND_CLERK_PUBLISHABLE_KEY || RUNTIME_CONFIG.NEEVCLOUD_DEMAND_CLERK_PUBLISHABLE_KEY || '',
    signInPath: '/demand/sign-in',
    signUpPath: '/demand/sign-up',
    accountPath: '/demand/account',
    submissionsPath: '/demand/submissions',
    formPath: '/find-compute',
    listApiPath: '/api/v1/me/demand-intake-sessions',
    detailApiBase: '/api/v1/demand-intake-sessions',
    accentClass: 'auth-page--demand',
  },
  supply: {
    label: 'Supply',
    portal: 'NeevCloud Supply Portal',
    publishableKey: window.NEEVCLOUD_SUPPLY_CLERK_PUBLISHABLE_KEY || RUNTIME_CONFIG.NEEVCLOUD_SUPPLY_CLERK_PUBLISHABLE_KEY || '',
    signInPath: '/supply/sign-in',
    signUpPath: '/supply/sign-up',
    accountPath: '/supply/account',
    submissionsPath: '/supply/submissions',
    formPath: '/submit-capacity',
    listApiPath: '/api/v1/me/supply-intake-sessions',
    detailApiBase: '/api/v1/supply-intake-sessions',
    accentClass: 'auth-page--supply',
  },
};

const clerkInstancePromises = {};
const clerkScriptPromises = {};

function getAdminAuthHashParams(hash = window.location.hash) {
  const rawHash = String(hash || '').replace(/^#/, '');
  const tokenStart = rawHash.indexOf('access_token=');
  if (tokenStart === -1) return null;
  return new URLSearchParams(rawHash.slice(tokenStart));
}

function captureAdminAuthFromUrl() {
  try {
    const params = getAdminAuthHashParams();
    const accessToken = params?.get('access_token');
    if (!accessToken) return false;
    const session = {
      access_token: accessToken,
      refresh_token: params.get('refresh_token') || '',
      expires_at: Number(params.get('expires_at')) || null,
      expires_in: Number(params.get('expires_in')) || null,
      token_type: params.get('token_type') || 'bearer',
      type: params.get('type') || '',
    };
    localStorage.setItem(ADMIN_TOKEN_KEY, accessToken);
    localStorage.setItem(ADMIN_SESSION_KEY, JSON.stringify(session));
    window.history.replaceState({}, document.title, `${window.location.origin}/admin`);
    return true;
  } catch {
    return false;
  }
}

function getCurrentRoute() {
  const authCaptured = captureAdminAuthFromUrl();
  if (authCaptured) return '/admin';
  if (redirectToCanonicalFrontendHost()) return '/';
  const path = window.location.pathname;
  const idnHost = window.location.hostname.toLowerCase().startsWith('idn.');
  const rawHash = window.location.hash.replace(/^#/, '');
  if ((path === '/' || path === '/index.html') && rawHash.startsWith('/')) {
    const legacyRoute = normalizeAppRoute(rawHash.split('#')[0] || '/');
    window.history.replaceState({}, document.title, legacyRoute);
    return legacyRoute;
  }
  if (path && path !== '/' && path !== '/index.html') return path;
  if (idnHost) return '/idn';
  return '/';
}

function getRouteAuthChannel(route) {
  const path = normalizeAppRoute(String(route || '/').split('?')[0].split('#')[0]);
  if (path === '/find-compute' || path.startsWith('/demand/')) return 'demand';
  if (path === '/submit-capacity' || path === '/submit-capacity/thank-you' || path.startsWith('/supply/')) return 'supply';
  return null;
}

function getAuthMock(channel) {
  const mock = window.__NEEV_AUTH_MOCK;
  if (!mock) return null;
  if (mock === true) {
    return {
      signedIn: true,
      token: `test-${channel}-token`,
      user: {
        id: `mock-${channel}-user`,
        fullName: `${AUTH_CHANNELS[channel].label} Test User`,
        primaryEmailAddress: { emailAddress: `${channel}@local.test` },
      },
    };
  }
  const channelMock = mock[channel] || mock;
  if (channelMock === false) return { signedIn: false };
  return {
    signedIn: channelMock.signedIn !== false,
    token: channelMock.token || `test-${channel}-token`,
    user: channelMock.user || {
      id: `mock-${channel}-user`,
      fullName: `${AUTH_CHANNELS[channel].label} Test User`,
      primaryEmailAddress: { emailAddress: `${channel}@local.test` },
    },
  };
}

function getAuthUserEmail(user) {
  return user?.primaryEmailAddress?.emailAddress || user?.emailAddresses?.[0]?.emailAddress || '';
}

function getClerkFrontendApi(publishableKey) {
  const encoded = String(publishableKey || '').split('_')[2];
  if (!encoded) return '';
  try {
    return atob(encoded).replace(/\$$/, '');
  } catch {
    return '';
  }
}

function loadScriptOnce(src, attrs = {}) {
  if (!src) return Promise.reject(new Error('Missing script URL.'));
  if (clerkScriptPromises[src]) return clerkScriptPromises[src];
  clerkScriptPromises[src] = new Promise((resolve, reject) => {
    const existing = document.querySelector(`script[src="${src}"]`);
    if (existing) {
      existing.addEventListener('load', () => resolve(), { once: true });
      existing.addEventListener('error', () => reject(new Error('Clerk failed to load.')), { once: true });
      return;
    }
    const script = document.createElement('script');
    script.src = src;
    script.async = true;
    script.crossOrigin = 'anonymous';
    Object.entries(attrs).forEach(([key, value]) => {
      if (value !== undefined && value !== null) script.setAttribute(key, value);
    });
    script.onload = () => resolve();
    script.onerror = () => reject(new Error('Clerk failed to load.'));
    document.head.appendChild(script);
  });
  return clerkScriptPromises[src];
}

async function loadClerkBrowserAssets(config) {
  if (window.Clerk) return;
  const frontendApi = getClerkFrontendApi(config.publishableKey);
  if (!frontendApi) throw new Error(`${config.portal} has an invalid Clerk publishable key.`);
  await loadScriptOnce(`https://${frontendApi}/npm/@clerk/ui@1/dist/ui.browser.js`, {
    'data-neev-clerk-ui': config.label.toLowerCase(),
  });
  const clerkJsUrl = CLERK_JS_URL || `https://${frontendApi}/npm/@clerk/clerk-js@6/dist/clerk.browser.js`;
  await loadScriptOnce(clerkJsUrl, {
    'data-neev-clerk-js': config.label.toLowerCase(),
    'data-clerk-publishable-key': config.publishableKey,
  });
}

async function getChannelClerk(channel) {
  const config = AUTH_CHANNELS[channel];
  if (!config) throw new Error('Unknown auth channel.');
  if (getAuthMock(channel)) return null;
  if (!config.publishableKey) throw new Error(`${config.portal} is missing its Clerk publishable key.`);
  if (!clerkInstancePromises[channel]) {
    clerkInstancePromises[channel] = (async () => {
      await loadClerkBrowserAssets(config);
      if (!window.Clerk) throw new Error('Clerk did not initialize.');
      const clerk = typeof window.Clerk === 'function'
        ? new window.Clerk(config.publishableKey)
        : window.Clerk;
      await clerk.load({
        ui: window.__internal_ClerkUICtor ? { ClerkUI: window.__internal_ClerkUICtor } : undefined,
        signInUrl: config.signInPath,
        signUpUrl: config.signUpPath,
        afterSignInUrl: config.formPath,
        afterSignUpUrl: config.formPath,
      });
      return clerk;
    })();
  }
  return clerkInstancePromises[channel];
}

async function getChannelAuthToken(channel) {
  const mock = getAuthMock(channel);
  if (mock) {
    if (!mock.signedIn) throw new Error(`${AUTH_CHANNELS[channel].portal} sign-in is required.`);
    return mock.token;
  }
  const clerk = await getChannelClerk(channel);
  if (!clerk?.session) throw new Error(`${AUTH_CHANNELS[channel].portal} sign-in is required.`);
  const token = await clerk.session.getToken();
  if (!token) throw new Error(`${AUTH_CHANNELS[channel].portal} sign-in is required.`);
  return token;
}

const ANSWER_KEY_MAP = {
  company_legal_name: 'company.legal_name',
  company_type: 'company.company_type',
  representative_type: 'company.representative_type',
  underlying_facility_owner: 'company.underlying_facility_owner',
  contact_full_name: 'contact.full_name',
  contact_email: 'contact.email',
  contact_phone_or_whatsapp: 'contact.phone_or_whatsapp',
  company_brand_name: 'company.brand_name',
  company_website: 'company.website',
  hq_country: 'company.hq_country',
  operating_countries: 'company.operating_countries',
  contact_job_title: 'contact.job_title',
  contact_linkedin: 'contact.linkedin_url',
  preferred_contact_method: 'contact.preferred_contact_method',
  contact_timezone: 'contact.time_zone',
  facility_name: 'facility.name',
  facility_address: 'facility.address',
  facility_city: 'facility.city',
  facility_country: 'facility.country',
  facility_stage: 'facility.stage',
  exclusivity_restrictions: 'facility.exclusivity_restrictions',
  facility_owner: 'facility.owner',
  facility_operator_name: 'facility.operator',
  facility_state: 'facility.state_region',
  facility_latitude: 'facility.latitude',
  facility_longitude: 'facility.longitude',
  existing_customer_profile: 'facility.existing_customer_profile',
  total_site_power_mw: 'power.total_site_power_mw',
  commissioned_power_mw: 'power.commissioned_power_mw',
  current_it_load_mw: 'power.current_it_load_mw',
  vacant_it_load_mw: 'power.vacant_it_load_mw',
  power_available_0_90_days_mw: 'power.available_0_90_days_mw',
  power_available_3_6_months_mw: 'power.available_3_6_months_mw',
  power_available_6_12_months_mw: 'power.available_6_12_months_mw',
  firm_expansion_path_30mw_18mo: 'power.firm_expansion_path_30mw_18mo',
  utility_provider: 'power.utility_provider',
  substation_feeder_detail: 'power.substation_feeder_detail',
  power_redundancy: 'power.power_redundancy',
  power_cost_per_kwh: 'power.estimated_power_cost_per_kwh',
  max_capacity_discussable_mw: 'power.max_capacity_discussable_mw',
  backup_power_setup: 'power.backup_power_setup',
  renewable_power_available: 'power.renewable_power_available',
  min_capacity_reservable_mw: 'power.min_capacity_reservable_mw',
  available_whitespace: 'space.available_whitespace',
  floor_loading_kg_per_m2: 'structural.floor_loading_kg_per_m2',
  cooling_type: 'cooling.cooling_type',
  current_rack_density_kw: 'cooling.current_rack_density_kw',
  max_rack_density_after_upgrade_kw: 'cooling.max_rack_density_after_upgrade_kw',
  liquid_cooling_readiness: 'cooling.liquid_cooling_readiness',
  cooling_upgrade_timeline: 'cooling.cooling_upgrade_timeline',
  water_availability_or_constraints: 'cooling.water_availability_or_constraints',
  heat_rejection_system: 'cooling.heat_rejection_system',
  cooling_expansion_constraints: 'cooling.expansion_constraints',
  available_whitespace_unit: 'space.available_whitespace_unit',
  ceiling_height_constraints: 'structural.ceiling_height_constraints',
  earliest_site_inspection_date: 'deployment.earliest_site_inspection_date',
  earliest_deployment_start_date: 'deployment.earliest_deployment_start_date',
  network_carriers: 'network.carriers',
  redundant_fiber_paths: 'network.redundant_fiber_paths',
  current_bandwidth_capacity: 'network.current_bandwidth_capacity',
  infiniband_roce_ndr_support: 'network.infiniband_roce_ndr_support',
  physical_security_model: 'ops.physical_security_model',
  onsite_staff_24_7: 'ops.onsite_staff_24_7',
  remote_hands_available: 'ops.remote_hands_available',
  fiber_providers: 'network.fiber_providers',
  internet_exchange_proximity: 'network.internet_exchange_proximity',
  cross_connect_available: 'network.cross_connect_available',
  expandable_bandwidth_capacity: 'network.expandable_bandwidth_capacity',
  network_buildout_required: 'network.network_buildout_required',
  noc_available: 'ops.noc_available',
  certifications: 'ops.certifications',
  data_residency_constraints: 'ops.data_residency_constraints',
  customer_audit_support: 'ops.customer_audit_support',
  site_access_process: 'ops.site_access_process',
  local_ops_support: 'ops.local_ops_support',
  willing_host_neevcloud_gpus: 'commercial.willing_host_neevcloud_gpus',
  willing_finance_gpus: 'commercial.willing_finance_gpus',
  preferred_commercial_model: 'commercial.preferred_commercial_model',
  open_to_first_right_refusal: 'commercial.open_to_first_right_refusal',
  permits_current_and_phase2: 'deployment.permits_current_and_phase2',
  major_upgrades_required: 'deployment.major_upgrades_required',
  agreement_to_ready_timeline: 'deployment.agreement_to_ready_timeline',
  willing_coinvest_gpus: 'commercial.willing_coinvest_gpus',
  open_to_loi: 'commercial.open_to_loi',
  open_to_mou: 'commercial.open_to_mou',
  open_to_minimum_guarantee: 'commercial.open_to_minimum_guarantee',
  open_to_take_or_pay: 'commercial.open_to_take_or_pay',
  minimum_commercial_term: 'commercial.minimum_commercial_term',
  target_monthly_revenue: 'commercial.target_monthly_revenue_or_return',
  lender_investor_approval: 'commercial.lender_or_investor_approval_required',
  gpu_ownership_constraints: 'commercial.gpu_ownership_import_financing_constraints',
  import_customs_support: 'deployment.import_customs_support',
  tax_regulatory_constraints: 'deployment.tax_or_regulatory_constraints',
  due_diligence_timeline: 'deployment.due_diligence_timeline',
  other_notes: 'deployment.other_notes',
};

const DEFAULT_DOCUMENT_LOCAL_KEY = 'doc_site_documents';
const DEFAULT_DOCUMENT_FILE_CATEGORY = 'site_documents';

const FILE_CATEGORY_MAP = {
  [DEFAULT_DOCUMENT_LOCAL_KEY]: DEFAULT_DOCUMENT_FILE_CATEGORY,
};

const BACKEND_KEY_MAP = Object.fromEntries(Object.entries(ANSWER_KEY_MAP).map(([localKey, backendKey]) => [backendKey, localKey]));
const FILE_QUESTION_MAP = Object.fromEntries(
  Object.entries(FILE_CATEGORY_MAP).map(([docKey, category]) => [`files.${category}`, docKey])
);
const QUESTION_SECTION_MAP = {
  company: 'company',
  contact: 'company',
  facility: 'facility',
  power: 'power',
  space: 'cooling',
  structural: 'cooling',
  cooling: 'cooling',
  network: 'network',
  ops: 'network',
  commercial: 'commercial',
  deployment: 'commercial',
  files: 'docs',
};

const DESIGN_MAP_NODES = [
  { id: 'us-va', x: 28.5, y: 28.3, tier: 1, liquid: true, city: 'N. Virginia', country: 'United States' },
  { id: 'us-sv', x: 16.1, y: 29.2, tier: 1, liquid: true, city: 'Silicon Valley', country: 'United States' },
  { id: 'us-la', x: 17.2, y: 31.1, tier: 2, liquid: false, city: 'Los Angeles', country: 'United States' },
  { id: 'us-chi', x: 25.7, y: 26.8, tier: 1, liquid: true, city: 'Chicago', country: 'United States' },
  { id: 'us-dal', x: 23.1, y: 31.8, tier: 1, liquid: true, city: 'Dallas', country: 'United States' },
  { id: 'us-atl', x: 26.6, y: 31.3, tier: 1, liquid: true, city: 'Atlanta', country: 'United States' },
  { id: 'us-nyc', x: 29.4, y: 27.4, tier: 1, liquid: true, city: 'New York', country: 'United States' },
  { id: 'us-den', x: 20.8, y: 27.9, tier: 1, liquid: true, city: 'Denver', country: 'United States' },
  { id: 'us-min', x: 24.1, y: 25, tier: 1, liquid: true, city: 'Minneapolis', country: 'United States' },
  { id: 'us-phx', x: 18.9, y: 31.4, tier: 1, liquid: true, city: 'Phoenix', country: 'United States' },
  { id: 'us-pdx', x: 15.9, y: 24.7, tier: 1, liquid: true, city: 'Portland', country: 'United States' },
  { id: 'us-wa', x: 16.7, y: 23.8, tier: 2, liquid: false, city: 'C. Washington', country: 'United States' },
  { id: 'ca-qc', x: 29.9, y: 24.2, tier: 1, liquid: true, city: 'Quebec', country: 'Canada' },
  { id: 'ca-tor', x: 28, y: 25.8, tier: 2, liquid: false, city: 'Toronto', country: 'Canada' },
  { id: 'br-sp', x: 37, y: 63.1, tier: 1, liquid: true, city: 'Sao Paulo', country: 'Brazil' },
  { id: 'co-bog', x: 29.4, y: 47.4, tier: 1, liquid: true, city: 'Bogota', country: 'Colombia' },
  { id: 'mx-mc', x: 22.5, y: 39.2, tier: 1, liquid: true, city: 'Mexico City', country: 'Mexico' },
  { id: 'cl-scl', x: 30.4, y: 68.6, tier: 1, liquid: true, city: 'Santiago', country: 'Chile' },
  { id: 'gb-lon', x: 50, y: 21.4, tier: 1, liquid: true, city: 'London', country: 'United Kingdom' },
  { id: 'nl-ams', x: 51.4, y: 20.9, tier: 1, liquid: true, city: 'Amsterdam', country: 'Netherlands' },
  { id: 'ie-dub', x: 48.3, y: 20.4, tier: 1, liquid: true, city: 'Dublin', country: 'Ireland' },
  { id: 'fr-par', x: 50.7, y: 22.9, tier: 1, liquid: true, city: 'Paris', country: 'France' },
  { id: 'de-fra', x: 52.4, y: 22.2, tier: 2, liquid: false, city: 'Frankfurt', country: 'Germany' },
  { id: 'se-sto', x: 55, y: 17, tier: 1, liquid: true, city: 'Stockholm', country: 'Sweden' },
  { id: 'dk-cph', x: 53.5, y: 19.1, tier: 2, liquid: false, city: 'Copenhagen', country: 'Denmark' },
  { id: 'no-osl', x: 53, y: 16.7, tier: 2, liquid: false, city: 'Oslo', country: 'Norway' },
  { id: 'es-mad', x: 49, y: 27.5, tier: 2, liquid: false, city: 'Madrid', country: 'Spain' },
  { id: 'is-rey', x: 43.9, y: 14.4, tier: 1, liquid: true, city: 'Reykjavik', country: 'Iceland' },
  { id: 'fi-hel', x: 56.9, y: 16.6, tier: 3, liquid: false, city: 'Helsinki', country: 'Finland' },
  { id: 'ru-mow', x: 60.5, y: 19, tier: 2, liquid: false, city: 'Moscow', country: 'Russia' },
  { id: 'ro-buc', x: 57.3, y: 25.3, tier: 3, liquid: false, city: 'Bucharest', country: 'Romania' },
  { id: 'tr-ist', x: 58.1, y: 27.2, tier: 3, liquid: false, city: 'Istanbul', country: 'Turkey' },
  { id: 'ae-auh', x: 65.1, y: 36.4, tier: 1, liquid: true, city: 'Abu Dhabi', country: 'UAE' },
  { id: 'il-tlv', x: 59.7, y: 32.2, tier: 1, liquid: true, city: 'Tel Aviv', country: 'Israel' },
  { id: 'za-cpt', x: 55.1, y: 68.8, tier: 2, liquid: false, city: 'Cape Town', country: 'South Africa' },
  { id: 'za-jnb', x: 57.8, y: 64.6, tier: 2, liquid: false, city: 'Johannesburg', country: 'South Africa' },
  { id: 'in-bom', x: 70.2, y: 39.4, tier: 1, liquid: true, city: 'Mumbai', country: 'India' },
  { id: 'in-hyd', x: 71.8, y: 40.3, tier: 2, liquid: false, city: 'Hyderabad', country: 'India' },
  { id: 'in-maa', x: 72.3, y: 42.7, tier: 2, liquid: false, city: 'Chennai', country: 'India' },
  { id: 'in-del', x: 71.5, y: 34.1, tier: 2, liquid: false, city: 'Delhi NCR', country: 'India' },
  { id: 'sg-sin', x: 78.8, y: 49.3, tier: 1, liquid: true, city: 'Singapore', country: 'Singapore' },
  { id: 'jp-tyo', x: 88.8, y: 30.2, tier: 1, liquid: true, city: 'Tokyo', country: 'Japan' },
  { id: 'jp-osa', x: 87.6, y: 30.7, tier: 1, liquid: true, city: 'Osaka', country: 'Japan' },
  { id: 'kr-sel', x: 85.3, y: 29.1, tier: 1, liquid: true, city: 'Seoul', country: 'South Korea' },
  { id: 'id-jkt', x: 79.7, y: 53.4, tier: 1, liquid: true, city: 'Jakarta', country: 'Indonesia' },
  { id: 'th-bkk', x: 77.9, y: 42.4, tier: 1, liquid: true, city: 'Bangkok', country: 'Thailand' },
  { id: 'hk-hkg', x: 81.7, y: 37.6, tier: 2, liquid: false, city: 'Hong Kong', country: 'Hong Kong' },
  { id: 'cn-bjs', x: 82.3, y: 27.8, tier: 3, liquid: false, city: 'China', country: 'China' },
  { id: 'au-syd', x: 92, y: 68.8, tier: 1, liquid: true, city: 'Sydney', country: 'Australia' },
  { id: 'au-mel', x: 90.3, y: 71, tier: 1, liquid: true, city: 'Melbourne', country: 'Australia' },
  { id: 'au-bne', x: 92.5, y: 65.3, tier: 1, liquid: true, city: 'Brisbane', country: 'Australia' },
  { id: 'au-per', x: 82.2, y: 67.8, tier: 1, liquid: true, city: 'Perth', country: 'Australia' },
  { id: 'au-adl', x: 88.5, y: 69.4, tier: 1, liquid: true, city: 'Adelaide', country: 'Australia' },
  { id: 'nz-akl', x: 98.5, y: 70.5, tier: 1, liquid: true, city: 'Auckland', country: 'New Zealand' },
];

const CAPACITY_LADDER = [
  {
    rung: 'Rung 01',
    k: 'Land + power',
    b: 'Site secured. Utility allocation in motion.',
    criteria: ['Land use approved', 'Utility LoI in hand', 'Substation pathway identified'],
    next: 'NeevCloud benchmarks your land and power package against current tender briefs.',
  },
  {
    rung: 'Rung 02',
    k: 'Powered shell',
    b: 'Shell complete. Power live. Fit-out pending.',
    criteria: ['Shell commissioned', 'Power available to bus', 'Fiber path scoped'],
    next: 'NeevCloud maps the shell against pending AI workload windows in the region.',
  },
  {
    rung: 'Rung 03',
    k: 'Fit-out ready',
    b: 'White-space ready. Awaiting tenant spec.',
    criteria: ['White-space metered', 'Mechanical loop verified', 'Operating readiness signed off'],
    next: 'Site enters NeevCloud Level 1 review queue. NDA is shared if relevant.',
  },
  {
    rung: 'Rung 04',
    k: 'High-density GPU ready',
    b: 'Engineered for 60-130 kW/rack.',
    criteria: ['Direct-to-chip path engineered', 'Floor loading over 1,800 kg/m2', 'Rack density target 60-130 kW'],
    next: 'High-density readiness is reviewed for future AI compute tender fit.',
  },
  {
    rung: 'Rung 05',
    k: 'Liquid-cooled cluster',
    b: 'Latest-generation GPU class.',
    criteria: ['Liquid loop verified', 'CDU and heat rejection sized', 'Latest-generation GPU class verified'],
    next: 'Reserved for Tier 1 technical review and tender-specific diligence.',
  },
];

const IDN_HOME_DATA = {
  demand: [
    { id: 'd-na', x: 24, y: 33, label: 'North America demand' },
    { id: 'd-eu', x: 51, y: 26, label: 'Europe demand' },
    { id: 'd-in', x: 69, y: 44, label: 'India demand' },
    { id: 'd-ap', x: 83, y: 44, label: 'APAC demand' },
  ],
  stats: [
    { v: '7', unit: '', k: 'regions with compute paths', note: 'demo demand coverage' },
    { v: 'H100', unit: '+', k: 'GPU class options', note: 'H100, H200, B200-class paths' },
    { v: '60-130', unit: 'kW', k: 'high-density rack support', note: 'for accelerator-heavy workloads' },
    { v: 'NDA', unit: '', k: 'private technical review', note: 'before detailed deployment exchange' },
  ],
  audiences: [
    {
      id: 'ai-labs',
      tab: 'AI labs',
      kicker: 'Inference demand',
      h: 'Launch regional inference without waiting on your own buildout.',
      b: 'Share your region, model profile, GPU class, and deployment window. NeevCloud helps you find a practical path to production capacity.',
      points: ['GPU class and serving profile reviewed together', 'Regional latency and scale requirements captured upfront', 'NDA before detailed capacity or site discussions'],
      cta: 'Find compute',
    },
    {
      id: 'enterprise',
      tab: 'Enterprise customers',
      kicker: 'Enterprise AI',
      h: 'Plan private AI capacity for regulated or high-volume workloads.',
      b: 'NeevCloud captures location, compliance, data-residency, networking, and procurement constraints before opening a deployment path.',
      points: ['Private deployment requirements captured early', 'Security and data-residency needs reviewed under NDA', 'Commercial process aligned to enterprise procurement'],
      cta: 'Find compute',
    },
    {
      id: 'neoclouds',
      tab: 'Neoclouds',
      kicker: 'Regional expansion',
      h: 'Expand into new regions before committing to a full local footprint.',
      b: 'Use NeevCloud to test demand, regional economics, and deployment timing for new GPU capacity lanes.',
      points: ['Regional expansion briefs handled privately', 'GPU class, network, and operations needs captured', 'Deployment windows compared before capex decisions'],
      cta: 'Find compute',
    },
    {
      id: 'app-platforms',
      tab: 'AI app platforms',
      kicker: 'Product inference',
      h: 'Keep user-facing AI products responsive as demand moves across regions.',
      b: 'NeevCloud records latency targets, traffic patterns, failover needs, and GPU preferences before recommending the right deployment route.',
      points: ['Latency and failover requirements captured', 'Launch and traffic ramp windows planned early', 'Capacity options reviewed before user growth is blocked'],
      cta: 'Find compute',
    },
  ],
  pipeline: [
    { n: '01', k: 'Compute brief', b: 'Share target regions, workload profile, GPU class, deployment window, and security constraints.', side: 'Brief' },
    { n: '02', k: 'Feasibility screen', b: 'NeevCloud checks which regional compute paths can support the requirement.', side: 'Review' },
    { n: '03', k: 'NDA and technical review', b: 'Detailed GPU, networking, compliance, and operating requirements are reviewed privately.', side: 'NDA' },
    { n: '04', k: 'Deployment proposal', b: 'NeevCloud comes back with a practical route for region, GPU class, timeline, and commercial discussion.', side: 'Plan' },
    { n: '05', k: 'Regional inference rollout', b: 'Capacity goes live after contracts, hardware, operations, and customer readiness align.', side: 'Live' },
  ],
  capacityTypes: [
    { k: 'Regional inference pod', b: 'For latency-sensitive serving in a specific geography or launch market.', spec: ['Regional launch', 'Low latency', 'Inference serving'] },
    { k: 'Dedicated GPU cluster', b: 'For sustained private model serving, product workloads, or reserved enterprise capacity.', spec: ['Committed cluster', 'Private serving', 'H100/H200 class'] },
    { k: 'High-density accelerator lane', b: 'For larger workloads that need 60-130 kW/rack design and liquid-ready operations.', spec: ['60-130 kW/rack', 'Liquid-ready', 'B200-class path'] },
    { k: 'Burst expansion window', b: 'For short launch windows, migration spikes, or fast demand tests in a new region.', spec: ['Temporary ramp', 'Fast review', 'Regional failover'] },
  ],
};

const SCHEMA_SECTION_MAP = {
  document_uploads: 'docs',
  company_contact: 'company',
  facility_identity: 'facility',
  power_roadmap: 'power',
  space_structural_cooling: 'cooling',
  network_ops: 'network',
  commercial_deployment: 'commercial',
  company_profile: 'company',
  primary_contact: 'company',
  power_capacity: 'power',
  ai_token_factory_readiness: 'cooling',
  cooling_rack_density: 'cooling',
  network_connectivity: 'network',
  security_compliance_operations: 'network',
  gpu_financing_commercial: 'commercial',
  deployment_constraints_timelines: 'commercial',
  documents_site_proof: 'docs',
};

const YES_NO_LOCAL_KEYS = new Set([
  'firm_expansion_path_30mw_18mo',
  'cross_connect_available',
  'redundant_fiber_paths',
  'onsite_staff_24_7',
  'remote_hands_available',
  'noc_available',
  'customer_audit_support',
  'local_ops_support',
  'willing_host_neevcloud_gpus',
  'willing_finance_gpus',
  'willing_coinvest_gpus',
  'open_to_loi',
  'open_to_mou',
  'open_to_first_right_refusal',
  'open_to_minimum_guarantee',
  'open_to_take_or_pay',
  'lender_investor_approval',
]);

const NUMERIC_LOCAL_KEYS = new Set([
  'total_site_power_mw',
  'commissioned_power_mw',
  'current_it_load_mw',
  'vacant_it_load_mw',
  'power_available_0_90_days_mw',
  'power_available_3_6_months_mw',
  'power_available_6_12_months_mw',
  'power_cost_per_kwh',
  'min_capacity_reservable_mw',
  'max_capacity_discussable_mw',
  'available_whitespace',
  'floor_loading_kg_per_m2',
  'current_rack_density_kw',
  'max_rack_density_after_upgrade_kw',
  'facility_latitude',
  'facility_longitude',
]);

const SINGLE_CHOICE_OPTIONS = {};
const MULTI_CHOICE_OPTIONS = {};

const CHOICE_ALIASES = {
  'company_type:data centre operator': 'Data center operator',
  'company_type:data center': 'Data center operator',
  'company_type:operator': 'Data center operator',
  'company_type:colo': 'Colocation provider',
  'company_type:colocation': 'Colocation provider',
  'company_type:real estate owner': 'Real estate / infra owner',
  'company_type:infrastructure owner': 'Real estate / infra owner',
  'company_type:neocloud': 'Cloud / Neocloud',
  'company_type:cloud': 'Cloud / Neocloud',
  'company_type:broker': 'Broker / advisor',
  'company_type:advisor': 'Broker / advisor',
  'representative_type:owner operator': 'Owner + operator',
  'representative_type:owner and operator': 'Owner + operator',
  'representative_type:direct owner operator': 'Owner + operator',
  'representative_type:broker': 'Broker / advisor',
  'representative_type:advisor': 'Broker / advisor',
  'facility_stage:powered': 'Powered shell',
  'facility_stage:data hall': 'Fitted data hall',
  'facility_stage:fitted': 'Fitted data hall',
  'facility_stage:live': 'Live facility',
  'firm_expansion_path_30mw_18mo:yes': 'Yes',
  'firm_expansion_path_30mw_18mo:no': 'No',
  'firm_expansion_path_30mw_18mo:unknown': 'Unknown',
  'cooling_type:liquid ready': 'Liquid-ready',
  'cooling_type:direct liquid cooling': 'Direct liquid',
  'network_carriers:jio': 'Reliance Jio',
  'infiniband_roce_ndr_support:infiniband': 'InfiniBand',
  'infiniband_roce_ndr_support:infinite band': 'InfiniBand',
  'infiniband_roce_ndr_support:ethernet 400g': 'Ethernet 400G+',
  'available_whitespace_unit:sq ft': 'sqft',
  'available_whitespace_unit:square feet': 'sqft',
  'available_whitespace_unit:square foot': 'sqft',
  'available_whitespace_unit:sq m': 'sqm',
  'available_whitespace_unit:square meters': 'sqm',
  'available_whitespace_unit:square metres': 'sqm',
};

const DISPLAY_OPTION_ALIASES = {
  'Maybe / needs discussion': 'Unsure',
  Unknown: 'Unsure',
  'Data-center operator': 'Data center operator',
  'Real estate / infrastructure owner': 'Real estate / infra owner',
  'Cloud / neocloud': 'Cloud / Neocloud',
  'Facility operator': 'Direct operator',
  PCI: 'PCI DSS',
  'Uptime Tier': 'Uptime Tier III',
  'GDPR readiness': 'GDPR-ready',
  'sq ft': 'sqft',
  'sq m': 'sqm',
};

const EMPTY_QUESTION_INDEX = {
  schemaVersion: null,
  fieldsByQuestion: {},
  allQuestionKeys: [],
  requiredQuestionKeys: [],
  requiredLocalKeys: [],
  sectionByQuestion: {},
  requiredBySection: {},
};

function displayOptionLabel(label) {
  return DISPLAY_OPTION_ALIASES[label] || label;
}

function buildQuestionIndex(schema) {
  if (!schema?.sections?.length) return EMPTY_QUESTION_INDEX;
  const fieldsByQuestion = {};
  const allQuestionKeys = [];
  const requiredQuestionKeys = [];
  const sectionByQuestion = {};
  const requiredBySection = {};

  schema.sections.forEach((section) => {
    const sectionId = SCHEMA_SECTION_MAP[section.id] || QUESTION_SECTION_MAP[String(section.id || '').split('_')[0]] || section.id;
    (section.fields || []).forEach((field) => {
      fieldsByQuestion[field.question_key] = { ...field, section_id: section.id, ui_section_id: sectionId };
      allQuestionKeys.push(field.question_key);
      sectionByQuestion[field.question_key] = sectionId;
      if (field.required) {
        requiredQuestionKeys.push(field.question_key);
        const localKey = BACKEND_KEY_MAP[field.question_key] || FILE_QUESTION_MAP[field.question_key];
        if (localKey) {
          requiredBySection[sectionId] = [...(requiredBySection[sectionId] || []), localKey];
        }
      }
    });
  });

  const canonicalRequired = schema.required_question_keys?.length
    ? schema.required_question_keys
    : requiredQuestionKeys;
  return {
    schemaVersion: schema.schema_version || null,
    fieldsByQuestion,
    allQuestionKeys,
    requiredQuestionKeys: canonicalRequired,
    requiredLocalKeys: canonicalRequired
      .map((questionKey) => BACKEND_KEY_MAP[questionKey] || FILE_QUESTION_MAP[questionKey])
      .filter(Boolean),
    sectionByQuestion,
    requiredBySection,
  };
}

function getFieldForQuestion(questionIndex, questionKey) {
  return questionIndex?.fieldsByQuestion?.[questionKey] || null;
}

function getSchemaOptionLabels(field) {
  return [...new Set((field?.options || [])
    .map((option) => displayOptionLabel(option.label || option.value))
    .filter(Boolean))];
}

function getSchemaOptionSearchItems(field) {
  return (field?.options || [])
    .flatMap((option) => [
      { match: option.value, label: displayOptionLabel(option.label || option.value) },
      { match: option.label, label: displayOptionLabel(option.label || option.value) },
    ])
    .filter((item) => item.match);
}

function getSchemaOptionValueSearchItems(field) {
  return (field?.options || [])
    .flatMap((option) => [
      { match: option.value, value: option.value },
      { match: option.label, value: option.value },
      { match: displayOptionLabel(option.label || option.value), value: option.value },
    ])
    .filter((item) => item.match);
}

function normalizeChoiceText(value) {
  return String(value || '')
    .toLowerCase()
    .replace(/&/g, ' and ')
    .replace(/\+/g, ' plus ')
    .replace(/[^a-z0-9]+/g, ' ')
    .replace(/\s+/g, ' ')
    .trim();
}

function normalizeYesNoValue(value) {
  const text = normalizeChoiceText(Array.isArray(value) ? value[0] : value);
  if (!text) return value;
  if (['yes', 'y', 'yeah', 'yep', 'sure', 'true', 'correct', 'affirmative'].includes(text) || text.startsWith('yes ')) return 'Yes';
  if (['no', 'n', 'nope', 'false', 'negative'].includes(text) || text.startsWith('no ') || text.includes('cannot') || text.includes('can not')) return 'No';
  if (text.includes('unsure') || text.includes('not sure') || text.includes('unknown') || text.includes('dont know') || text.includes('do not know') || text.includes('maybe')) return 'Unsure';
  return value;
}

function parseSpokenNumber(value) {
  if (typeof value === 'number') return value;
  const raw = String(value || '').toLowerCase().replace(/,/g, '');
  const numeric = raw.match(/-?\d+(\.\d+)?/);
  if (numeric) return Number(numeric[0]);

  const words = raw
    .replace(/-/g, ' ')
    .replace(/[^a-z\s.]+/g, ' ')
    .replace(/\s+/g, ' ')
    .trim()
    .split(' ')
    .filter(Boolean);
  if (!words.length) return null;

  const small = {
    zero: 0, one: 1, two: 2, three: 3, four: 4, five: 5, six: 6, seven: 7, eight: 8, nine: 9,
    ten: 10, eleven: 11, twelve: 12, thirteen: 13, fourteen: 14, fifteen: 15, sixteen: 16,
    seventeen: 17, eighteen: 18, nineteen: 19,
  };
  const tens = { twenty: 20, thirty: 30, forty: 40, fourty: 40, fifty: 50, sixty: 60, seventy: 70, eighty: 80, ninety: 90 };
  const valid = new Set([...Object.keys(small), ...Object.keys(tens), 'hundred', 'thousand', 'point', 'and']);
  let total = 0;
  let current = 0;
  let consumed = false;
  let decimal = '';
  let afterPoint = false;

  for (const word of words) {
    if (!valid.has(word)) {
      if (consumed) break;
      continue;
    }
    if (word === 'and') continue;
    consumed = true;
    if (word === 'point') {
      afterPoint = true;
      continue;
    }
    if (afterPoint) {
      const digit = small[word];
      if (digit !== undefined && digit < 10) decimal += String(digit);
      continue;
    }
    if (small[word] !== undefined) current += small[word];
    else if (tens[word] !== undefined) current += tens[word];
    else if (word === 'hundred') current = Math.max(1, current) * 100;
    else if (word === 'thousand') {
      total += Math.max(1, current) * 1000;
      current = 0;
    }
  }

  if (!consumed) return null;
  const integer = total + current;
  return Number(`${integer}${decimal ? `.${decimal}` : ''}`);
}

function matchChoice(localKey, rawValue, options = [], schemaItems = []) {
  const raw = String(rawValue || '').trim();
  const normalized = normalizeChoiceText(raw);
  if (!normalized) return rawValue;
  const alias = CHOICE_ALIASES[`${localKey}:${normalized}`];
  if (alias) return alias;
  const schemaMatch = schemaItems.find((item) => normalizeChoiceText(item.match) === normalized);
  if (schemaMatch) return schemaMatch.label;
  const exact = options.find((option) => normalizeChoiceText(option) === normalized);
  if (exact) return exact;
  const schemaContained = schemaItems.find((item) => {
    const optionNorm = normalizeChoiceText(item.match);
    return normalized.includes(optionNorm) || optionNorm.includes(normalized);
  });
  if (schemaContained) return schemaContained.label;
  const contained = options.find((option) => {
    const optionNorm = normalizeChoiceText(option);
    return normalized.includes(optionNorm) || optionNorm.includes(normalized);
  });
  return contained || raw;
}

function splitChoiceValues(value) {
  if (Array.isArray(value)) return value;
  return String(value || '')
    .split(/[,;]|\band\b|\n/i)
    .map((item) => item.trim())
    .filter(Boolean);
}

function pad2(value) {
  return String(value).padStart(2, '0');
}

function normalizeDateValue(value) {
  if (!value) return value;
  if (value instanceof Date && !Number.isNaN(value.getTime())) {
    return `${value.getFullYear()}-${pad2(value.getMonth() + 1)}-${pad2(value.getDate())}`;
  }
  const raw = String(value || '').trim();
  if (/^\d{4}-\d{2}-\d{2}$/.test(raw)) return raw;

  const slash = raw.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/);
  if (slash) {
    const month = Number(slash[1]);
    const day = Number(slash[2]);
    const year = Number(slash[3]);
    if (month >= 1 && month <= 12 && day >= 1 && day <= 31) return `${year}-${pad2(month)}-${pad2(day)}`;
  }

  const monthNames = {
    january: 1, jan: 1, february: 2, feb: 2, march: 3, mar: 3, april: 4, apr: 4,
    may: 5, june: 6, jun: 6, july: 7, jul: 7, august: 8, aug: 8,
    september: 9, sep: 9, sept: 9, october: 10, oct: 10, november: 11, nov: 11,
    december: 12, dec: 12,
  };
  const cleaned = raw.toLowerCase().replace(/,/g, ' ').replace(/(\d+)(st|nd|rd|th)/g, '$1').replace(/\s+/g, ' ').trim();
  const parts = cleaned.split(' ');
  let month = null;
  let day = null;
  let year = null;
  parts.forEach((part) => {
    if (monthNames[part]) month = monthNames[part];
    else if (/^\d{4}$/.test(part)) year = Number(part);
    else if (/^\d{1,2}$/.test(part) && day === null) day = Number(part);
  });
  if (month && day) {
    const now = new Date();
    year = year || now.getFullYear();
    const candidate = new Date(year, month - 1, day);
    if (!Number.isNaN(candidate.getTime())) {
      if (!parts.some((part) => /^\d{4}$/.test(part)) && candidate < new Date(now.getFullYear(), now.getMonth(), now.getDate())) {
        year += 1;
      }
      return `${year}-${pad2(month)}-${pad2(day)}`;
    }
  }
  return value;
}

function matchCanonicalChoice(localKey, rawValue, field) {
  const raw = String(rawValue || '').trim();
  const normalized = normalizeChoiceText(raw);
  if (!normalized) return rawValue;
  const alias = CHOICE_ALIASES[`${localKey}:${normalized}`];
  const schemaItems = getSchemaOptionValueSearchItems(field);
  if (alias) {
    const aliasMatch = schemaItems.find((item) => normalizeChoiceText(item.match) === normalizeChoiceText(alias));
    if (aliasMatch) return aliasMatch.value;
  }
  const exact = schemaItems.find((item) => normalizeChoiceText(item.match) === normalized);
  if (exact) return exact.value;
  const contained = schemaItems.find((item) => {
    const optionNorm = normalizeChoiceText(item.match);
    return normalized.includes(optionNorm) || optionNorm.includes(normalized);
  });
  return contained?.value || raw;
}

function normalizeAnswerForBackend(questionKey, value, questionIndex = EMPTY_QUESTION_INDEX) {
  const localKey = BACKEND_KEY_MAP[questionKey] || FILE_QUESTION_MAP[questionKey];
  const field = getFieldForQuestion(questionIndex, questionKey);
  if (!localKey || !field) return value;
  if (value === undefined || value === null || value === '') return value;
  const literalValue = String(Array.isArray(value) ? value[0] : value).trim().toLowerCase();
  if (literalValue === 'unknown' && field.allow_unknown) return 'unknown';
  if ((literalValue === 'not_applicable' || literalValue === 'not applicable' || literalValue === 'n/a') && field.allow_not_applicable) {
    return 'not_applicable';
  }

  if (field.field_type === 'number' && typeof value === 'string') {
    const parsed = parseSpokenNumber(value);
    return Number.isFinite(parsed) ? parsed : value;
  }
  if (field.field_type === 'date') return normalizeDateValue(value);
  if (field.field_type === 'select') return matchCanonicalChoice(localKey, value, field);
  if (field.field_type === 'multiselect' || field.field_type === 'multiselect_freeform') {
    return splitChoiceValues(value).map((item) => matchCanonicalChoice(localKey, item, field));
  }
  return value;
}

function validateBackendAnswerValue(questionKey, value, questionIndex = EMPTY_QUESTION_INDEX) {
  const field = getFieldForQuestion(questionIndex, questionKey);
  if (!field || value === undefined || value === null || value === '') return { ok: true };
  const literalValue = String(Array.isArray(value) ? value[0] : value).trim().toLowerCase();
  if (literalValue === 'unknown' && field.allow_unknown) return { ok: true };
  if ((literalValue === 'not_applicable' || literalValue === 'not applicable' || literalValue === 'n/a') && field.allow_not_applicable) {
    return { ok: true };
  }
  if (field.field_type === 'number') {
    const parsed = typeof value === 'number' ? value : parseSpokenNumber(value);
    if (!Number.isFinite(parsed)) {
      return { ok: false, message: `${field.label} needs a number only. Please repeat it as digits, without units.` };
    }
  }
  if (field.field_type === 'date' && !/^\d{4}-\d{2}-\d{2}$/.test(String(value))) {
    return { ok: false, message: `${field.label} needs a date in YYYY-MM-DD format.` };
  }
  if ((field.field_type === 'multiselect' || field.field_type === 'multiselect_freeform') && !Array.isArray(value)) {
    return { ok: false, message: `${field.label} needs a list of values.` };
  }
  return { ok: true };
}

function normalizeBackendAnswerValue(questionKey, value, questionIndex = EMPTY_QUESTION_INDEX) {
  const localKey = BACKEND_KEY_MAP[questionKey] || FILE_QUESTION_MAP[questionKey];
  if (!localKey) return value;
  const field = getFieldForQuestion(questionIndex, questionKey);
  const fieldType = field?.field_type;
  const schemaOptionLabels = getSchemaOptionLabels(field);
  const schemaSearchItems = getSchemaOptionSearchItems(field);
  const localSingleOptions = SINGLE_CHOICE_OPTIONS[localKey] || [];
  const localMultiOptions = MULTI_CHOICE_OPTIONS[localKey] || [];
  const choiceOptions = schemaOptionLabels.length
    ? schemaOptionLabels
    : [...localSingleOptions, ...localMultiOptions];
  const literalValue = String(Array.isArray(value) ? value[0] : value).trim().toLowerCase();
  if (literalValue === 'unknown' && field?.allow_unknown) return 'unknown';
  if ((literalValue === 'not_applicable' || literalValue === 'not applicable' || literalValue === 'n/a') && field?.allow_not_applicable) {
    return 'not_applicable';
  }

  if (YES_NO_LOCAL_KEYS.has(localKey) || (fieldType === 'select' && schemaOptionLabels.includes('Yes') && schemaOptionLabels.some((label) => label === 'No'))) {
    return normalizeYesNoValue(value);
  }
  if ((NUMERIC_LOCAL_KEYS.has(localKey) || fieldType === 'number') && typeof value === 'string') {
    const parsed = parseSpokenNumber(value);
    return Number.isFinite(parsed) ? parsed : value;
  }
  if (fieldType === 'date') {
    return normalizeDateValue(value);
  }
  if (MULTI_CHOICE_OPTIONS[localKey] || fieldType === 'multiselect' || fieldType === 'multiselect_freeform') {
    const normalized = splitChoiceValues(value).map((item) => matchChoice(localKey, item, choiceOptions, schemaSearchItems));
    return [...new Set(normalized.filter(Boolean))];
  }
  if (SINGLE_CHOICE_OPTIONS[localKey] || fieldType === 'select') {
    const first = Array.isArray(value) ? value[0] : value;
    return matchChoice(localKey, first, choiceOptions, schemaSearchItems);
  }
  return value;
}

function pushFormSyncLog(entry) {
  const item = { ts: Date.now(), ...entry };
  window.__NEEV_FORM_SYNC_LOGS = [...(window.__NEEV_FORM_SYNC_LOGS || []), item].slice(-200);
  console.info('[form-sync]', item);
}

const REQUIRED_LOCAL_KEYS = [
  'company_legal_name', 'company_type', 'representative_type',
  'contact_full_name', 'contact_email', 'contact_phone_or_whatsapp',
  'facility_name', 'facility_city', 'facility_country', 'facility_stage',
  'total_site_power_mw', 'vacant_it_load_mw', 'power_available_3_6_months_mw',
  'earliest_deployment_start_date', 'current_rack_density_kw', 'max_rack_density_after_upgrade_kw',
  'cooling_type', 'willing_host_neevcloud_gpus',
];

function toBackendAnswers(answers, questionIndex = EMPTY_QUESTION_INDEX) {
  const out = {};
  const validQuestionKeys = new Set(questionIndex?.allQuestionKeys || []);
  const canSend = (questionKey) => validQuestionKeys.size === 0 || validQuestionKeys.has(questionKey);
  Object.entries(ANSWER_KEY_MAP).forEach(([localKey, backendKey]) => {
    const value = answers[localKey];
    if (value === undefined || value === null || value === '') return;
    if (Array.isArray(value) && value.length === 0) return;
    if (!canSend(backendKey)) return;
    out[backendKey] = normalizeAnswerForBackend(backendKey, value, questionIndex);
  });

  if (answers.facility_latlng && typeof answers.facility_latlng === 'string') {
    const [lat, lng] = answers.facility_latlng.split(',').map((v) => Number(v.trim()));
    if (Number.isFinite(lat) && canSend('facility.latitude')) out['facility.latitude'] = lat;
    if (Number.isFinite(lng) && canSend('facility.longitude')) out['facility.longitude'] = lng;
  }
  return out;
}

function backendAnswersToLocal(backendAnswers, questionIndex = EMPTY_QUESTION_INDEX) {
  const out = {};
  Object.entries(backendAnswers || {}).forEach(([questionKey, value]) => {
    const localKey = BACKEND_KEY_MAP[questionKey] || FILE_QUESTION_MAP[questionKey];
    if (!localKey) return;
    out[localKey] = normalizeBackendAnswerValue(questionKey, value, questionIndex);
  });
  return out;
}

async function apiFetch(path, options = {}) {
  const { authChannel, ...fetchOptions } = options;
  const authHeaders = authChannel
    ? { Authorization: `Bearer ${await getChannelAuthToken(authChannel)}` }
    : {};
  const response = await fetch(`${API_BASE}${path}`, {
    ...fetchOptions,
    headers: {
      ...(fetchOptions.body instanceof FormData ? {} : { 'Content-Type': 'application/json' }),
      ...authHeaders,
      ...(fetchOptions.headers || {}),
    },
  });
  const payload = await response.json().catch(() => ({}));
  if (!response.ok) {
    const message = payload?.detail?.message || payload?.detail || payload?.message || `Request failed (${response.status})`;
    const err = new Error(typeof message === 'string' ? message : JSON.stringify(message));
    err.payload = payload;
    err.status = response.status;
    throw err;
  }
  return payload;
}

function getAdminAccessToken() {
  try {
    const session = JSON.parse(localStorage.getItem(ADMIN_SESSION_KEY) || '{}');
    return session.access_token || localStorage.getItem(ADMIN_TOKEN_KEY) || '';
  } catch {
    return localStorage.getItem(ADMIN_TOKEN_KEY) || '';
  }
}

async function adminApiFetch(path, options = {}) {
  const token = getAdminAccessToken();
  const response = await fetch(`${API_BASE}${path}`, {
    ...options,
    headers: {
      ...(options.body instanceof FormData ? {} : { 'Content-Type': 'application/json' }),
      ...(token ? { Authorization: `Bearer ${token}` } : {}),
      ...(options.headers || {}),
    },
  });
  const payload = await response.json().catch(() => ({}));
  if (!response.ok) {
    const message = payload?.detail?.message || payload?.detail || payload?.message || `Request failed (${response.status})`;
    const err = new Error(typeof message === 'string' ? message : JSON.stringify(message));
    err.payload = payload;
    err.status = response.status;
    throw err;
  }
  return payload;
}

function FormProvider({ children }) {
  const [answers, setAnswers] = useState(() => {
    try {
      const raw = localStorage.getItem(STORAGE_KEY);
      return raw ? JSON.parse(raw) : {};
    } catch { return {}; }
  });
  const [sessionId, setSessionId] = useState(() => {
    try {
      return JSON.parse(localStorage.getItem(META_KEY) || '{}').sessionId || null;
    } catch { return null; }
  });
  const [submission, setSubmission] = useState(() => {
    try {
      return JSON.parse(localStorage.getItem(META_KEY) || '{}').submission || null;
    } catch { return null; }
  });
  const [formSchema, setFormSchema] = useState(null);
  const [schemaStatus, setSchemaStatus] = useState({ state: 'loading', message: 'Loading form schema...' });
  const [documentStatus, setDocumentStatus] = useState(null);
  const [prefillReview, setPrefillReview] = useState(null);
  const [prefillStatus, setPrefillStatus] = useState({ state: 'idle', message: '' });
  const [savedAt, setSavedAt] = useState(null);
  const [apiStatus, setApiStatus] = useState({ state: 'idle', message: '' });
  const [remoteDraftReady, setRemoteDraftReady] = useState(false);
  const lastSyncedRef = useRef('');
  const sessionIdRef = useRef(sessionId);
  const submissionRef = useRef(submission);
  const sessionPromiseRef = useRef(null);
  const questionIndex = useMemo(() => buildQuestionIndex(formSchema), [formSchema]);

  useEffect(() => {
    sessionIdRef.current = sessionId;
  }, [sessionId]);

  useEffect(() => {
    submissionRef.current = submission;
  }, [submission]);

  useEffect(() => {
    let cancelled = false;
    (async () => {
      try {
        const schema = await apiFetch('/api/v1/intake-schema/supply');
        if (cancelled) return;
        setFormSchema(schema);
        setSchemaStatus({ state: 'ready', message: `Schema ${schema.schema_version || ''}`.trim() });
        pushFormSyncLog({
          action: 'schema_loaded',
          schemaVersion: schema.schema_version,
          requiredQuestionKeys: schema.required_question_keys || [],
        });
      } catch (err) {
        if (cancelled) return;
        setSchemaStatus({ state: 'error', message: `Schema load failed: ${err.message}` });
        pushFormSyncLog({ action: 'schema_load_failed', message: err.message });
      }
    })();
    return () => { cancelled = true; };
  }, []);

  const ensureSession = async (channel = 'web_form') => {
    if (sessionIdRef.current) return sessionIdRef.current;
    if (sessionPromiseRef.current) return sessionPromiseRef.current;
    sessionPromiseRef.current = (async () => {
      setApiStatus({ state: 'connecting', message: 'Creating secure intake session...' });
      const session = await apiFetch('/api/v1/supply-intake-sessions', {
        method: 'POST',
        authChannel: 'supply',
        body: JSON.stringify({ channel }),
      });
      sessionIdRef.current = session.id;
      setSessionId(session.id);
      localStorage.setItem(META_KEY, JSON.stringify({ sessionId: session.id, submission: submissionRef.current }));
      setApiStatus({ state: 'idle', message: '' });
      return session.id;
    })();
    try {
      return await sessionPromiseRef.current;
    } finally {
      sessionPromiseRef.current = null;
    }
  };

  useEffect(() => {
    if (!questionIndex.schemaVersion) return undefined;
    let cancelled = false;

    const loadDetail = async (id) => {
      if (!id) return null;
      try {
        return await apiFetch(`/api/v1/supply-intake-sessions/${id}`, { authChannel: 'supply' });
      } catch (err) {
        pushFormSyncLog({ action: 'remote_supply_draft_detail_failed', sessionId: id, message: err.message });
        return null;
      }
    };

    (async () => {
      try {
        let detail = await loadDetail(sessionIdRef.current);
        if (!detail) {
          const list = await apiFetch('/api/v1/me/supply-intake-sessions', { authChannel: 'supply' }).catch((err) => {
            pushFormSyncLog({ action: 'remote_supply_draft_list_failed', message: err.message });
            return null;
          });
          const latestDraft = (list?.sessions || []).find((item) => item.status === 'draft');
          detail = await loadDetail(latestDraft?.session_id);
        }
        if (cancelled || !detail?.session) return;
        const loadedSessionId = detail.session.id;
        const loadedAnswers = backendAnswersToLocal(detail.answers || {}, questionIndex);
        setAnswers((current) => {
          const next = Object.keys(current || {}).length ? { ...loadedAnswers, ...current } : loadedAnswers;
          lastSyncedRef.current = JSON.stringify(toBackendAnswers(next, questionIndex));
          localStorage.setItem(STORAGE_KEY, JSON.stringify(next));
          return next;
        });
        sessionIdRef.current = loadedSessionId;
        setSessionId(loadedSessionId);
        localStorage.setItem(META_KEY, JSON.stringify({ sessionId: loadedSessionId, submission: submissionRef.current }));
        pushFormSyncLog({ action: 'remote_supply_draft_loaded', sessionId: loadedSessionId });
      } finally {
        if (!cancelled) setRemoteDraftReady(true);
      }
    })();

    return () => { cancelled = true; };
  }, [questionIndex.schemaVersion]);

  useEffect(() => {
    if (!remoteDraftReady) return undefined;
    const t = setTimeout(() => {
      localStorage.setItem(STORAGE_KEY, JSON.stringify(answers));
      const backendAnswers = toBackendAnswers(answers, questionIndex);
      if (Object.keys(backendAnswers).length === 0) {
        setSavedAt(new Date());
        return;
      }
      const syncSignature = JSON.stringify(backendAnswers);
      if (syncSignature === lastSyncedRef.current) {
        setSavedAt(new Date());
        return;
      }
      (async () => {
        try {
          const activeSessionId = await ensureSession();
          setApiStatus({ state: 'saving', message: 'Saving draft...' });
          await apiFetch(`/api/v1/supply-intake-sessions/${activeSessionId}`, {
            method: 'PATCH',
            authChannel: 'supply',
            body: JSON.stringify({ answered_by: 'web_form', answers: backendAnswers }),
          });
          lastSyncedRef.current = syncSignature;
          setSavedAt(new Date());
          setApiStatus({ state: 'idle', message: '' });
        } catch (err) {
          setApiStatus({ state: 'error', message: `Autosave failed: ${err.message}` });
        }
      })();
    }, 350);
    return () => clearTimeout(t);
  }, [answers, questionIndex, remoteDraftReady]);

  const setAnswer = (key, val) => setAnswers(prev => ({ ...prev, [key]: val }));
  const setBackendAnswer = (questionKey, value, options = {}) => {
    const { markSynced = true } = options;
    const localKey = BACKEND_KEY_MAP[questionKey] || FILE_QUESTION_MAP[questionKey];
    if (!localKey) {
      pushFormSyncLog({ action: 'unmapped_question_key', questionKey, value });
      return false;
    }
    const normalizedValue = normalizeBackendAnswerValue(questionKey, value, questionIndex);
    const validation = validateBackendAnswerValue(questionKey, normalizedValue, questionIndex);
    if (!validation.ok) {
      pushFormSyncLog({ action: 'invalid_answer_format', questionKey, value, normalizedValue, message: validation.message });
      return false;
    }
    setAnswers(prev => {
      const next = { ...prev, [localKey]: normalizedValue };
      if (markSynced) lastSyncedRef.current = JSON.stringify(toBackendAnswers(next, questionIndex));
      localStorage.setItem(STORAGE_KEY, JSON.stringify(next));
      return next;
    });
    pushFormSyncLog({ action: 'visible_answer_set', questionKey, localKey, rawValue: value, normalizedValue, markSynced, schemaVersion: questionIndex.schemaVersion });
    setSavedAt(new Date());
    return true;
  };
  const resetLocalDraft = () => {
    setAnswers({});
    sessionIdRef.current = null;
    submissionRef.current = null;
    setSessionId(null);
    setSubmission(null);
    setDocumentStatus(null);
    setPrefillReview(null);
    setPrefillStatus({ state: 'idle', message: '' });
    lastSyncedRef.current = '';
    sessionPromiseRef.current = null;
    localStorage.removeItem(STORAGE_KEY);
    localStorage.removeItem(META_KEY);
  };
  const reset = () => {
    resetLocalDraft();
  };

  const resetVoiceIntake = async (payload = {}) => {
    const previousSessionId = sessionIdRef.current;
    resetLocalDraft();
    setApiStatus({ state: 'saving', message: 'Starting a fresh intake draft...' });

    const abandonPromise = previousSessionId
      ? apiFetch(`/api/v1/supply-intake-sessions/${previousSessionId}/abandon`, {
        method: 'POST',
        authChannel: 'supply',
        body: JSON.stringify({ reason: payload.reason || 'User asked the voice agent to start over.' }),
      }).catch((error) => {
        pushFormSyncLog({ action: 'abandon_previous_session_failed', sessionId: previousSessionId, message: error.message });
        return null;
      })
      : Promise.resolve(null);

    const newSessionId = await ensureSession('voice_agent');
    const [abandonedSession, voiceState] = await Promise.all([
      abandonPromise,
      apiFetch(`/api/v1/supply-intake-sessions/${newSessionId}/voice/state`, { authChannel: 'supply' }),
    ]);
    setApiStatus({ state: 'idle', message: '' });
    pushFormSyncLog({
      action: 'voice_reset_started_new_draft',
      previousSessionId,
      newSessionId,
      previousSessionAbandoned: Boolean(abandonedSession),
    });
    return {
      ok: true,
      action: 'started_new_draft',
      message: 'Visible form cleared. Fresh draft started.',
      previous_session_id: previousSessionId,
      session_id: newSessionId,
      previous_session_abandoned: Boolean(abandonedSession),
      voice_state: voiceState,
    };
  };

  const refreshDocumentStatus = async () => {
    const activeSessionId = await ensureSession();
    const status = await apiFetch(`/api/v1/supply-intake-sessions/${activeSessionId}/documents/status`, { authChannel: 'supply' });
    setDocumentStatus(status);
    return status;
  };

  const refreshPrefillReview = async () => {
    const activeSessionId = await ensureSession();
    const review = await apiFetch(`/api/v1/supply-intake-sessions/${activeSessionId}/prefill-review`, { authChannel: 'supply' });
    setPrefillReview(review);
    return review;
  };

  const extractDocuments = async (fileIds = [], candidateAnswers = []) => {
    const activeSessionId = await ensureSession();
    setPrefillStatus({ state: 'processing', message: 'Checking uploaded documents...' });
    const result = await apiFetch(`/api/v1/supply-intake-sessions/${activeSessionId}/documents/extract`, {
      method: 'POST',
      authChannel: 'supply',
      body: JSON.stringify({
        file_ids: fileIds.filter(Boolean),
        candidate_answers: candidateAnswers,
      }),
    });
    for (const extracted of result.extracted_answers || []) {
      if (extracted.status === 'auto_accepted' && extracted.question_key) {
        setBackendAnswer(extracted.question_key, extracted.normalized_value ?? extracted.candidate_value);
      }
    }
    await Promise.allSettled([refreshDocumentStatus(), refreshPrefillReview()]);
    setPrefillStatus({
      state: 'idle',
      message: result.extracted_answers?.length
        ? `Found ${result.extracted_answers.length} candidate answer${result.extracted_answers.length === 1 ? '' : 's'}.`
        : 'Documents queued for extraction.',
    });
    return result;
  };

  const acceptPrefillAnswer = async (extractedAnswerId) => {
    const activeSessionId = await ensureSession();
    setPrefillStatus({ state: 'saving', message: 'Accepting extracted answer...' });
    const result = await apiFetch(`/api/v1/supply-intake-sessions/${activeSessionId}/prefill-review/${extractedAnswerId}/accept`, {
      method: 'POST',
      authChannel: 'supply',
      body: JSON.stringify({}),
    });
    const extracted = result.extracted_answer;
    if (extracted?.question_key) {
      setBackendAnswer(extracted.question_key, extracted.normalized_value ?? extracted.candidate_value);
    }
    await Promise.allSettled([refreshPrefillReview(), refreshDocumentStatus()]);
    setPrefillStatus({ state: 'idle', message: 'Extracted answer accepted.' });
    return result;
  };

  const rejectPrefillAnswer = async (extractedAnswerId) => {
    const activeSessionId = await ensureSession();
    setPrefillStatus({ state: 'saving', message: 'Rejecting extracted answer...' });
    const result = await apiFetch(`/api/v1/supply-intake-sessions/${activeSessionId}/prefill-review/${extractedAnswerId}/reject`, {
      method: 'POST',
      authChannel: 'supply',
      body: JSON.stringify({}),
    });
    await Promise.allSettled([refreshPrefillReview(), refreshDocumentStatus()]);
    setPrefillStatus({ state: 'idle', message: 'Extracted answer rejected.' });
    return result;
  };

  const uploadFile = async (docKey, file) => {
    const activeSessionId = await ensureSession();
    const fileCategory = FILE_CATEGORY_MAP[docKey] || DEFAULT_DOCUMENT_FILE_CATEGORY;
    if (!fileCategory) throw new Error(`Unsupported document slot: ${docKey}`);
    const body = new FormData();
    body.append('file_category', fileCategory);
    body.append('file', file);
    setApiStatus({ state: 'saving', message: `Uploading ${file.name}...` });
    const uploaded = await apiFetch(`/api/v1/supply-intake-sessions/${activeSessionId}/documents`, {
      method: 'POST',
      authChannel: 'supply',
      body,
    });
    setApiStatus({ state: 'idle', message: '' });
    const metadata = {
      name: uploaded.original_filename,
      size: uploaded.size_bytes,
      uploadedAt: uploaded.uploaded_at,
      storagePath: uploaded.storage_path,
      fileId: uploaded.id,
      fileCategory: uploaded.file_category,
    };
    setAnswers(prev => {
      const current = prev[docKey];
      const currentList = Array.isArray(current) ? current : (current ? [current] : []);
      return { ...prev, [docKey]: [metadata, ...currentList] };
    });
    await Promise.allSettled([refreshDocumentStatus(), refreshPrefillReview()]);
    return metadata;
  };

  const uploadArtifact = async (file, fileCategory = DEFAULT_DOCUMENT_FILE_CATEGORY) => {
    const activeSessionId = await ensureSession('voice_agent');
    const body = new FormData();
    body.append('file_category', fileCategory);
    body.append('file', file);
    setApiStatus({ state: 'saving', message: `Uploading ${file.name}...` });
    const uploaded = await apiFetch(`/api/v1/supply-intake-sessions/${activeSessionId}/documents`, {
      method: 'POST',
      authChannel: 'supply',
      body,
    });
    setApiStatus({ state: 'idle', message: '' });
    await Promise.allSettled([refreshDocumentStatus(), refreshPrefillReview()]);
    return uploaded;
  };

  const getVoiceState = async () => {
    const activeSessionId = await ensureSession('voice_agent');
    return apiFetch(`/api/v1/supply-intake-sessions/${activeSessionId}/voice/state`, { authChannel: 'supply' });
  };

  const getVoiceToken = async () => {
    const activeSessionId = await ensureSession('voice_agent');
    return apiFetch(`/api/v1/supply-intake-sessions/${activeSessionId}/voice/token`, {
      method: 'POST',
      authChannel: 'supply',
      body: JSON.stringify({}),
    });
  };

  const registerVoiceConversation = async (elevenlabsConversationId, metadata = {}) => {
    const activeSessionId = await ensureSession('voice_agent');
    return apiFetch(`/api/v1/supply-intake-sessions/${activeSessionId}/voice/conversations`, {
      method: 'POST',
      authChannel: 'supply',
      body: JSON.stringify({ elevenlabs_conversation_id: elevenlabsConversationId, status: 'active', metadata }),
    });
  };

  const proposeVoiceAnswer = async (payload) => {
    const activeSessionId = await ensureSession('voice_agent');
    const result = await apiFetch(`/api/v1/supply-intake-sessions/${activeSessionId}/voice/answers/propose`, {
      method: 'POST',
      authChannel: 'supply',
      body: JSON.stringify(payload),
    });
    if (['saved', 'verified', 'overwritten'].includes(result.action) && result.question_key) {
      setBackendAnswer(result.question_key, result.current_value);
    }
    return result;
  };

  const confirmVoiceOverwrite = async (payload) => {
    const activeSessionId = await ensureSession('voice_agent');
    const result = await apiFetch(`/api/v1/supply-intake-sessions/${activeSessionId}/voice/answers/confirm`, {
      method: 'POST',
      authChannel: 'supply',
      body: JSON.stringify(payload),
    });
    if (result.question_key) setBackendAnswer(result.question_key, result.current_value);
    return result;
  };

  const markVoiceUnresolved = async (payload) => {
    const activeSessionId = await ensureSession('voice_agent');
    return apiFetch(`/api/v1/supply-intake-sessions/${activeSessionId}/voice/unresolved`, {
      method: 'POST',
      authChannel: 'supply',
      body: JSON.stringify(payload),
    });
  };

  const prepareVoiceReview = async () => {
    const activeSessionId = await ensureSession('voice_agent');
    return apiFetch(`/api/v1/supply-intake-sessions/${activeSessionId}/voice/review`, {
      method: 'POST',
      authChannel: 'supply',
      body: JSON.stringify({}),
    });
  };

  const attachArtifactLink = async ({ file_category = DEFAULT_DOCUMENT_FILE_CATEGORY, url, label, question_key }) => {
    const activeSessionId = await ensureSession('voice_agent');
    const result = await apiFetch(`/api/v1/supply-intake-sessions/${activeSessionId}/files/link`, {
      method: 'POST',
      authChannel: 'supply',
      body: JSON.stringify({ file_category, url, label, question_key }),
    });
    if (question_key) {
      const docKey = FILE_QUESTION_MAP[question_key];
      if (docKey) {
        setAnswers(prev => ({ ...prev, [docKey]: {
          name: result.original_filename,
          size: result.size_bytes,
          uploadedAt: result.uploaded_at,
          storagePath: result.storage_path,
          fileId: result.id,
          fileCategory: result.file_category,
        } }));
      }
    }
    return result;
  };

  useEffect(() => {
    window.__NEEV_FORM_ANSWERS = answers;
    window.__NEEV_FORM_SESSION_ID = sessionId;
    window.__NEEV_DEBUG_FORM = () => ({
      sessionId,
      answers,
      backendAnswers: toBackendAnswers(answers, questionIndex),
      formSchema,
      questionIndex,
      documentStatus,
      prefillReview,
      syncLogs: window.__NEEV_FORM_SYNC_LOGS || [],
      voiceToolLogs: window.__NEEV_VOICE_TOOL_LOGS || [],
      latencyLogs: window.__NEEV_VOICE_LATENCY_LOGS || [],
    });
  }, [answers, sessionId, formSchema, questionIndex, documentStatus, prefillReview]);

  useEffect(() => {
    if (!sessionId) return;
    Promise.allSettled([refreshDocumentStatus(), refreshPrefillReview()]);
  }, [sessionId]);

  const submit = async () => {
    const activeSessionId = await ensureSession();
    const backendAnswers = toBackendAnswers(answers, questionIndex);
    setApiStatus({ state: 'saving', message: 'Saving final answers...' });
    await apiFetch(`/api/v1/supply-intake-sessions/${activeSessionId}`, {
      method: 'PATCH',
      authChannel: 'supply',
      body: JSON.stringify({ answered_by: 'web_form', answers: backendAnswers }),
    });
    setApiStatus({ state: 'submitting', message: 'Submitting to NeevCloud...' });
    const result = await apiFetch(`/api/v1/supply-intake-sessions/${activeSessionId}/submit`, {
      method: 'POST',
      authChannel: 'supply',
      body: JSON.stringify({}),
    });
    setSubmission(result);
    localStorage.setItem(META_KEY, JSON.stringify({ sessionId: activeSessionId, submission: result }));
    setApiStatus({ state: 'submitted', message: 'Submitted.' });
    return result;
  };

  return (
    <FormCtx.Provider value={{
      answers,
      setAnswer,
      setBackendAnswer,
      savedAt,
      reset,
      sessionId,
      ensureSession,
      apiStatus,
      schemaStatus,
      formSchema,
      questionIndex,
      documentStatus,
      prefillReview,
      prefillStatus,
      uploadFile,
      uploadArtifact,
      extractDocuments,
      refreshDocumentStatus,
      refreshPrefillReview,
      acceptPrefillAnswer,
      rejectPrefillAnswer,
      submit,
      submission,
      getVoiceState,
      getVoiceToken,
      registerVoiceConversation,
      proposeVoiceAnswer,
      confirmVoiceOverwrite,
      markVoiceUnresolved,
      prepareVoiceReview,
      resetVoiceIntake,
      attachArtifactLink,
      backendKeyMap: BACKEND_KEY_MAP,
      fileQuestionMap: FILE_QUESTION_MAP,
      questionSectionMap: QUESTION_SECTION_MAP,
      normalizeBackendAnswer: (questionKey, value) => normalizeBackendAnswerValue(questionKey, value, questionIndex),
      validateBackendAnswer: (questionKey, value) => validateBackendAnswerValue(questionKey, value, questionIndex),
    }}>
      {children}
    </FormCtx.Provider>
  );
}

function useChannelAuthState(channel) {
  const [state, setState] = useState({ loading: true, signedIn: false, user: null, error: '' });

  useEffect(() => {
    let cancelled = false;
    let unsubscribe = null;
    const mock = getAuthMock(channel);
    if (mock) {
      setState({ loading: false, signedIn: Boolean(mock.signedIn), user: mock.user || null, error: '' });
      return undefined;
    }

    (async () => {
      try {
        const clerk = await getChannelClerk(channel);
        const update = () => {
          if (cancelled) return;
          setState({
            loading: false,
            signedIn: Boolean(clerk?.session),
            user: clerk?.user || null,
            error: '',
          });
        };
        update();
        unsubscribe = clerk?.addListener?.(update) || null;
      } catch (err) {
        if (!cancelled) {
          setState({ loading: false, signedIn: false, user: null, error: err.message });
        }
      }
      return undefined;
    })();

    return () => {
      cancelled = true;
      if (unsubscribe) unsubscribe();
    };
  }, [channel]);

  return state;
}

function ClerkMount({ channel, mode, returnTo }) {
  const mountRef = useRef(null);
  const [error, setError] = useState('');

  useEffect(() => {
    let disposed = false;
    let clerkRef = null;
    const config = AUTH_CHANNELS[channel];
    const mountName = mode === 'sign-up' ? 'mountSignUp' : mode === 'account' ? 'mountUserProfile' : 'mountSignIn';
    const unmountName = mode === 'sign-up' ? 'unmountSignUp' : mode === 'account' ? 'unmountUserProfile' : 'unmountSignIn';

    (async () => {
      try {
        const clerk = await getChannelClerk(channel);
        clerkRef = clerk;
        if (disposed || !mountRef.current) return;
        if (!clerk?.[mountName]) throw new Error('This Clerk bundle cannot mount the requested auth view.');
        const redirectPath = returnTo || config.formPath;
        const options = {
          signInUrl: config.signInPath,
          signUpUrl: config.signUpPath,
          afterSignInUrl: redirectPath,
          afterSignUpUrl: redirectPath,
        };
        if (mode === 'account') {
          clerk[mountName](mountRef.current);
        } else {
          clerk[mountName](mountRef.current, options);
        }
      } catch (err) {
        if (!disposed) setError(err.message);
      }
    })();

    return () => {
      disposed = true;
      if (clerkRef?.[unmountName] && mountRef.current) {
        clerkRef[unmountName](mountRef.current);
      } else if (mountRef.current) {
        mountRef.current.innerHTML = '';
      }
    };
  }, [channel, mode, returnTo]);

  return (
    <>
      {error && <div className="admin-error">{error}</div>}
      <div className="clerk-mount" ref={mountRef} />
    </>
  );
}

function ChannelAuthShell({ channel, children }) {
  const config = AUTH_CHANNELS[channel];
  return (
    <div className={`page auth-page ${config.accentClass}`}>
      <TopNav mode={channel === 'demand' ? 'idn' : 'capacity'} />
      <main className="auth-main">
        {children}
      </main>
    </div>
  );
}

function ChannelAuthPage({ channel, mode = 'sign-in', embedded = false, returnTo = null }) {
  const { nav } = useRoute();
  const config = AUTH_CHANNELS[channel];
  const auth = useChannelAuthState(channel);
  const mock = getAuthMock(channel);
  const title = mode === 'sign-up' ? `Create your ${config.label.toLowerCase()} account` : `Sign in to ${config.portal}`;
  const shell = (content) => embedded ? content : <ChannelAuthShell channel={channel}>{content}</ChannelAuthShell>;

  if (auth.loading) {
    return shell(<div className="admin-empty">Loading {config.portal}...</div>);
  }

  if (auth.error) {
    return shell(
      <section className="auth-card">
        <div className="kicker"><span className="dot" /> {config.portal}</div>
        <h1 className="serif">{config.label} login is not configured.</h1>
        <p>{auth.error}</p>
      </section>
    );
  }

  if (auth.signedIn && mode !== 'account') {
    return shell(
      <section className="auth-card">
        <div className="kicker"><span className="dot" /> {config.portal}</div>
        <h1 className="serif">You are signed in.</h1>
        <p>{getAuthUserEmail(auth.user) || 'Your account is ready for this portal.'}</p>
        <div className="auth-actions">
          <button className="btn btn--primary" onClick={() => nav(config.formPath)}>Open form</button>
          <button className="btn btn--ghost" onClick={() => nav(config.submissionsPath)}>View submissions</button>
        </div>
      </section>
    );
  }

  if (mock && !auth.signedIn) {
    return shell(
      <section className="auth-card">
        <div className="kicker"><span className="dot" /> {config.portal}</div>
        <h1 className="serif">Sign-in required.</h1>
        <p>The test auth mock is configured as signed out for this channel.</p>
      </section>
    );
  }

  return shell(
    <section className="auth-card auth-card--mount">
      <div className="auth-card__copy">
        <div className="kicker"><span className="dot" /> {config.portal}</div>
        <h1 className="serif">{title}</h1>
        <p>{config.label} accounts are separate from the other NeevCloud portal.</p>
        {mode === 'sign-in' ? (
          <button className="linkbtn" onClick={() => nav(config.signUpPath)}>Create account</button>
        ) : (
          <button className="linkbtn" onClick={() => nav(config.signInPath)}>I already have an account</button>
        )}
      </div>
      <ClerkMount channel={channel} mode={mode} returnTo={returnTo || config.formPath} />
    </section>
  );
}

function ChannelAuthGate({ channel, children }) {
  const config = AUTH_CHANNELS[channel];
  const auth = useChannelAuthState(channel);
  if (auth.loading) {
    return (
      <ChannelAuthShell channel={channel}>
        <div className="admin-empty">Loading {config.portal}...</div>
      </ChannelAuthShell>
    );
  }
  if (auth.error || !auth.signedIn) {
    return (
      <ChannelAuthShell channel={channel}>
        <ChannelAuthPage channel={channel} mode="sign-in" embedded returnTo={config.formPath} />
      </ChannelAuthShell>
    );
  }
  return children;
}

function ChannelAccount({ channel }) {
  const config = AUTH_CHANNELS[channel];
  const auth = useChannelAuthState(channel);
  const mock = getAuthMock(channel);
  return (
    <ChannelAuthShell channel={channel}>
      <section className="auth-card auth-card--mount">
        <div className="auth-card__copy">
          <div className="kicker"><span className="dot" /> {config.portal}</div>
          <h1 className="serif">{config.label} account</h1>
          <p>{getAuthUserEmail(auth.user) || 'Manage the profile connected to this portal.'}</p>
        </div>
        {mock ? (
          <div className="mock-account">
            <b>{auth.user?.fullName || 'Mock user'}</b>
            <span>{getAuthUserEmail(auth.user)}</span>
          </div>
        ) : (
          <ClerkMount channel={channel} mode="account" />
        )}
      </section>
    </ChannelAuthShell>
  );
}

function formatSubmissionDate(value) {
  if (!value) return 'No timestamp';
  const date = new Date(value);
  if (Number.isNaN(date.getTime())) return String(value);
  return new Intl.DateTimeFormat(undefined, { month: 'short', day: 'numeric', year: 'numeric' }).format(date);
}

function ChannelSubmissions({ channel }) {
  const { nav } = useRoute();
  const config = AUTH_CHANNELS[channel];
  const [state, setState] = useState({ loading: true, error: '', sessions: [] });

  useEffect(() => {
    let cancelled = false;
    (async () => {
      try {
        const payload = await apiFetch(config.listApiPath, { authChannel: channel });
        if (!cancelled) setState({ loading: false, error: '', sessions: payload.sessions || [] });
      } catch (err) {
        if (!cancelled) setState({ loading: false, error: err.message, sessions: [] });
      }
    })();
    return () => { cancelled = true; };
  }, [channel]);

  const openSession = (session) => {
    if (channel === 'supply') {
      localStorage.setItem(META_KEY, JSON.stringify({ sessionId: session.session_id, submission: null }));
    } else {
      localStorage.setItem(DEMAND_META_KEY, JSON.stringify({ sessionId: session.session_id }));
    }
    nav(config.formPath);
  };

  const startNew = () => {
    if (channel === 'supply') {
      localStorage.removeItem(META_KEY);
      localStorage.removeItem(STORAGE_KEY);
    } else {
      localStorage.removeItem(DEMAND_META_KEY);
      localStorage.removeItem(DEMAND_STORAGE_KEY);
    }
    nav(config.formPath);
  };

  return (
    <ChannelAuthShell channel={channel}>
      <section className="submissions-head">
        <div>
          <div className="kicker"><span className="dot" /> {config.portal}</div>
          <h1 className="serif">{config.label} submissions</h1>
          <p>Resume drafts and review submitted intake records for this portal.</p>
        </div>
        <button className="btn btn--primary" onClick={startNew}>Start new</button>
      </section>
      {state.loading ? <div className="admin-empty">Loading submissions...</div> : state.error ? <div className="admin-error">{state.error}</div> : (
        <section className="submission-list">
          {state.sessions.map((session) => (
            <article className="submission-row" key={session.session_id}>
              <div>
                <span className="tag">{session.status || 'draft'}</span>
                <h2>{session.company_or_title || (channel === 'demand' ? 'Compute request' : 'Capacity submission')}</h2>
                <p>{session.reference || 'No reference yet'} · Updated {formatSubmissionDate(session.updated_at)}</p>
              </div>
              <button className="btn btn--ghost" onClick={() => openSession(session)}>
                {session.status === 'submitted' ? 'Open' : 'Resume'}
              </button>
            </article>
          ))}
          {!state.sessions.length && (
            <div className="admin-empty">No {config.label.toLowerCase()} submissions yet.</div>
          )}
        </section>
      )}
    </ChannelAuthShell>
  );
}

// =========================================================================
// APP ROOT
// =========================================================================
function App() {
  const [t] = useState(window.NEEV_TWEAKS_DEFAULTS || {
    theme: 'light',
    accent: '#8DC50E',
    density: 'roomy',
    heroLayout: 'map',
    showMap: true,
    voiceDemo: 'idle',
    voicePanel: 'collapsed',
  });

  useEffect(() => {
    const root = document.documentElement;
    root.dataset.theme = 'lumen';
    root.style.setProperty('--density', t.density === 'compact' ? '0.85' : '1');
  }, [t.density]);

  return (
    <RouteProvider>
      <Shell t={t} />
    </RouteProvider>
  );
}

const lightPalette = {
  '--paper': '#F5F2EC',
  '--paper-2': '#EDE8DE',
  '--paper-3': '#E2DCCE',
  '--ink': '#15140F',
  '--ink-2': '#3A372E',
  '--ink-3': '#6A6555',
  '--ink-4': '#94907F',
  '--rule': '#1514141A',
  '--rule-strong': '#15141433',
  '--card': '#FBFAF6',
};

const darkPalette = {
  '--paper': '#15140F',
  '--paper-2': '#1D1B15',
  '--paper-3': '#26241C',
  '--ink': '#F5F2EC',
  '--ink-2': '#D4CFBF',
  '--ink-3': '#A39E8C',
  '--ink-4': '#6A6555',
  '--rule': '#F5F2EC1A',
  '--rule-strong': '#F5F2EC33',
  '--card': '#1A1813',
};

function Shell({ t }) {
  const { route } = useRoute();

  useEffect(() => {
    document.title = pageTitleForRoute(route);
  }, [route]);

  let screen;
  if (route === '/' || route === '') screen = <Landing t={t} />;
  else if (route === '/idn') screen = <IDNHome />;
  else if (route === '/find-compute') screen = <ChannelAuthGate channel="demand"><FindCompute /></ChannelAuthGate>;
  else if (route === '/submit-capacity') screen = <ChannelAuthGate channel="supply"><FormProvider><SupplyForm /></FormProvider></ChannelAuthGate>;
  else if (route === '/submit-capacity/thank-you') screen = <ChannelAuthGate channel="supply"><FormProvider><ThankYou /></FormProvider></ChannelAuthGate>;
  else if (route === '/demand/sign-in') screen = <ChannelAuthPage channel="demand" mode="sign-in" />;
  else if (route === '/demand/sign-up') screen = <ChannelAuthPage channel="demand" mode="sign-up" />;
  else if (route === '/demand/account') screen = <ChannelAuthGate channel="demand"><ChannelAccount channel="demand" /></ChannelAuthGate>;
  else if (route === '/demand/submissions') screen = <ChannelAuthGate channel="demand"><ChannelSubmissions channel="demand" /></ChannelAuthGate>;
  else if (route === '/supply/sign-in') screen = <ChannelAuthPage channel="supply" mode="sign-in" />;
  else if (route === '/supply/sign-up') screen = <ChannelAuthPage channel="supply" mode="sign-up" />;
  else if (route === '/supply/account') screen = <ChannelAuthGate channel="supply"><ChannelAccount channel="supply" /></ChannelAuthGate>;
  else if (route === '/supply/submissions') screen = <ChannelAuthGate channel="supply"><ChannelSubmissions channel="supply" /></ChannelAuthGate>;
  else if (route === '/admin/login') screen = <AdminLogin />;
  else if (route === '/admin') screen = <AdminDashboard />;
  else if (route === '/admin/demand') screen = <AdminDemandDashboard />;
  else if (route.startsWith('/admin/demand/')) screen = <AdminDemandDetail requestId={route.split('/').pop()} />;
  else if (route.startsWith('/admin/applications/')) screen = <AdminApplicationDetail applicationId={route.split('/').pop()} />;
  else if (route === '/admin/criteria') screen = <AdminCriteria />;
  else screen = <Landing t={t} />;

  return screen;
}

function pageTitleForRoute(route) {
  if (route === '/idn') return 'NeevCloud IDN - Find AI Compute';
  if (route === '/find-compute') return 'Find Compute - NeevCloud IDN';
  if (route === '/submit-capacity') return 'Register DC Capacity - NeevCloud IDN';
  if (route === '/submit-capacity/thank-you') return 'DC Capacity Registered - NeevCloud IDN';
  if (route?.startsWith('/demand/')) return 'Demand Portal - NeevCloud IDN';
  if (route?.startsWith('/supply/')) return 'Supply Portal - NeevCloud IDN';
  if (route === '/admin/login') return 'Admin Login - NeevCloud IDN';
  if (route === '/admin') return 'Admin Review Queue - NeevCloud IDN';
  if (route === '/admin/demand') return 'Demand Requests - NeevCloud IDN';
  if (route?.startsWith('/admin/demand/')) return 'Demand Request Detail - NeevCloud IDN';
  if (route?.startsWith('/admin/applications/')) return 'Application Detail - NeevCloud IDN';
  if (route === '/admin/criteria') return 'Site Selection Criteria - NeevCloud IDN';
  return 'DC Capacity Registration - NeevCloud IDN';
}

// =========================================================================
// LANDING
// =========================================================================
function Landing() {
  const { nav } = useRoute();
  return (
    <div className="page">
      <TopNav mode="capacity" />
      <main className="landing">
        <section className="hero" id="atlas">
          <div className="hero__stage">
            <div className="hero__stage-map">
              <CapacityWorldMap />
            </div>
            <div className="hero__stage-overlay">
              <div className="kicker"><span className="dot" /> NeevCloud DC capacity network</div>
              <h1 className="hero__h1">Register GPU-ready data center capacity</h1>
            </div>
          </div>
          <div className="hero__below">
            <p className="hero__lede">
              Share power, cooling, rack density, and deployment timing so NeevCloud can review your site for future AI compute tenders.
            </p>
            <div className="hero__cta">
              <button className="btn btn--primary btn--lg" onClick={() => nav('/submit-capacity')}>
                Register DC capacity
              </button>
            </div>
            <div className="hero__trust">
              <span>Map markers are representative references only and do not imply partnership, endorsement, live capacity, purchase, or commercial commitment.</span>
            </div>
          </div>
        </section>

        <HowItWorks />
        <FitLadder />
        <ReviewQueuePreview nav={nav} />
        <LandingFinalCTA nav={nav} />
        <Footer />
      </main>
    </div>
  );
}

function TopNav({ mode = 'capacity' }) {
  const { nav } = useRoute();
  const isIdn = mode === 'idn';
  const openSupport = () => { window.location.href = 'https://www.neevcloud.com/support.php'; };
  const links = isIdn
    ? [
      ['Capacity atlas', '/idn#atlas'],
      ["Who it's for", '/idn#who'],
      ['How it works', '/idn#how'],
      ['Compute options', '/idn#capacity'],
    ]
    : [
      ['Capacity atlas', '/#atlas'],
      ['How it works', '/#how'],
      ['Fit ladder', '/#fit'],
      ['Review queue', '/#queue'],
    ];
  return (
    <header className="topnav">
      <div className="topnav__inner">
        <a
          className="brand"
          href={isIdn ? '/idn' : '/'}
          aria-label={isIdn ? 'NeevCloud IDN' : 'NeevCloud capacity'}
          onClick={(event) => { event.preventDefault(); nav(isIdn ? '/idn' : '/'); }}
        >
          <img className="brand__logo" src={NEEVCLOUD_LOGO_SRC} alt="NeevCloud" />
          <span className="brand__sep" aria-hidden="true">/</span>
          <span className="brand__sub">{isIdn ? 'IDN' : 'DC Capacity'}</span>
        </a>
        <nav className="topnav__links" aria-label="Primary">
          {links.map(([label, href]) => (
            <a key={href} href={href} onClick={(event) => { event.preventDefault(); nav(href); }}>
              {label}
            </a>
          ))}
        </nav>
        <div className="topnav__actions">
          {isIdn && (
            <>
              <button className="btn btn--compute btn--sm" onClick={() => nav('/find-compute')}>Find compute</button>
              <button className="btn btn--ghost btn--sm" onClick={openSupport}>Talk to NeevCloud</button>
            </>
          )}
          {!isIdn && (
            <button className="btn btn--primary btn--sm" onClick={() => nav('/submit-capacity')}>
              Register DC capacity
            </button>
          )}
        </div>
      </div>
    </header>
  );
}

function CapacityWorldMap() {
  const nodes = DESIGN_MAP_NODES;
  const [filter, setFilter] = useState({ tier: 'all', cooling: 'all' });
  const [active, setActive] = useState('in-bom');
  const matches = (node) => {
    if (filter.tier !== 'all' && node.tier !== filter.tier) return false;
    if (filter.cooling === 'liquid' && !node.liquid) return false;
    if (filter.cooling === 'air' && node.liquid) return false;
    return true;
  };
  const chip = (group, value, label) => (
    <button
      key={`${group}-${value}`}
      type="button"
      className={`w-map__chip ${filter[group] === value ? 'w-map__chip--on' : ''}`}
      onClick={() => setFilter((current) => ({ ...current, [group]: value }))}
    >
      {label}
    </button>
  );
  return (
    <div className="w-map" aria-label="Representative global data center capacity map">
      <img className="w-map__base" src="assets/world-map.svg" alt="" aria-hidden="true" />
      <div className="w-map__filters">
        {chip('tier', 'all', 'All tiers')}
        {chip('tier', 1, 'Tier 1')}
        {chip('tier', 2, 'Tier 2')}
        {chip('tier', 3, 'Tier 3')}
        <span style={{ width: 12 }} />
        {chip('cooling', 'all', 'Any cooling')}
        {chip('cooling', 'liquid', 'Liquid-ready')}
        {chip('cooling', 'air', 'Air')}
      </div>
      <div className="w-map__overlay">
        {nodes.map((node) => {
          const visible = matches(node);
          return (
            <button
              key={node.id}
              type="button"
              className={`w-map__node w-map__node--tier${node.tier}${node.liquid ? ' w-map__node--liquid' : ''}${active === node.id ? ' w-map__node--active' : ''}`}
              style={{ left: `${node.x}%`, top: `${node.y}%`, opacity: visible ? 1 : 0.15, pointerEvents: visible ? 'auto' : 'none' }}
              onClick={() => setActive(node.id)}
              aria-label={`${node.city}, ${node.country}`}
            >
              <span className="w-map__node-pulse" />
              <span className="w-map__node-dot" />
            </button>
          );
        })}
      </div>
      <div className="w-map__legend">
        <span><i className="t1" /> Tier 1 · high-density ready</span>
        <span><i className="t2" /> Tier 2 · sourced</span>
        <span><i className="t3" /> Tier 3 · under review</span>
      </div>
    </div>
  );
}

function HowItWorks() {
  const steps = [
    ['01', 'Upload site documents', 'Drop RFI files, floor plans, photos, power letters, diagrams, carrier sheets, or a ZIP bundle.'],
    ['02', 'Complete Level 1 registration', 'Answer the short public intake for ownership, location, power, cooling, timing, and GPU hosting posture.'],
    ['03', 'NeevCloud reviews fit', 'The infrastructure team checks region, available power, rack density, cooling path, site readiness, and commercial fit.'],
    ['04', 'Enter future tender consideration', 'Qualified sites may be invited into deeper diligence when a relevant AI compute tender opens.'],
  ];
  return (
    <section className="section" id="how">
      <div className="section__rule"><span>How it works</span><span>Document-first registration</span></div>
      <div className="section__head">
        <div><h2 className="h-title">From application to first tender consideration.</h2></div>
        <p>NeevCloud only asks for enough public information to place your DC site into Level 1 review. Deeper diagrams and commercial diligence come later, under the right context.</p>
      </div>
      <div className="steps">
        {steps.map(([n, k, b]) => (
          <article className="step" key={n}>
            <div className="step__n">{n}</div>
            <h3 className="step__k">{k}</h3>
            <p className="step__b">{b}</p>
          </article>
        ))}
      </div>
    </section>
  );
}

function FitLadder() {
  const [active, setActive] = useState(2);
  const item = CAPACITY_LADDER[active];
  return (
    <section className="section" id="fit">
      <div className="section__rule"><span>Fit ladder</span><span>How site readiness is discussed</span></div>
      <div className="section__head">
        <div><h2 className="h-title">NeevCloud reviews capacity by actual deployment readiness.</h2></div>
        <p>Applicants do not need to expose every diligence detail in the public form. The Level 1 flow captures the signals needed to decide whether a deeper technical review is worth opening.</p>
      </div>
      <div className="w-ladder">
        <div className="w-ladder__list">
          {CAPACITY_LADDER.map((step, index) => (
            <button
              key={step.rung}
              type="button"
              className={`w-ladder__step ${index === active ? 'w-ladder__step--on' : ''}`}
              onClick={() => setActive(index)}
            >
              <span className="w-ladder__step-rung">{step.rung}</span>
              <span className="w-ladder__step-copy">
                <span className="w-ladder__step-k">{step.k}</span>
                <span className="w-ladder__step-b">{step.b}</span>
              </span>
              <span className="w-ladder__step-chev">-&gt;</span>
            </button>
          ))}
        </div>
        <div className="w-ladder__detail">
          <div className="kicker"><span className="dot" /> {item.rung}</div>
          <h4>{item.k}</h4>
          <ul className="w-ladder__detail-criteria">
            {item.criteria.map((criterion) => <li key={criterion}>{criterion}</li>)}
          </ul>
          <div className="w-ladder__detail-next">
            <div className="w-ladder__detail-next-l">Next move</div>
            <div className="w-ladder__detail-next-b">{item.next}</div>
          </div>
        </div>
      </div>
    </section>
  );
}

function ReviewQueuePreview({ nav }) {
  const journey = [
    ['Level 1 qualification', 'NeevCloud reviews your registered site for AI-ready fit.'],
    ['NDA if relevant', 'Detailed exchange only starts when the site and demand context justify it.'],
    ['Technical design exchange', 'Power, cooling, density, network and operations are reviewed together.'],
    ['Tender review', 'A site may be reviewed against a region, GPU lane, and deployment window.'],
  ];
  return (
    <section className="section" id="queue">
      <div className="section__rule"><span>Review queue</span><span>What happens after you register</span></div>
      <div className="section__head">
        <div><h2 className="h-title">Registration makes your DC capacity visible to NeevCloud IDN.</h2></div>
        <p>Registration does not guarantee a tender, reservation, purchase, deployment, or commercial commitment. It gives NeevCloud enough signal to know when to follow up.</p>
      </div>
      <div className="w-journey">
        <h3 className="w-journey__h">Review path</h3>
        <div className="w-journey__track">
          <div className="w-journey__rail"><div className="w-journey__rail-fill" style={{ width: '58%' }} /></div>
          {journey.map(([k, b], index) => (
            <article key={k} className={`w-journey__step ${index < 2 ? 'w-journey__step--done' : index === 2 ? 'w-journey__step--active' : ''}`}>
              <span className="w-journey__node">{String(index + 1).padStart(2, '0')}</span>
              <h4 className="w-journey__k">{k}</h4>
              <p className="w-journey__b">{b}</p>
            </article>
          ))}
        </div>
      </div>
      <div className="cta-strip">
        <h3 className="cta-strip__h">Ready to register a site?</h3>
        <button className="btn btn--primary btn--lg" onClick={() => nav('/submit-capacity')}>Register DC capacity</button>
      </div>
    </section>
  );
}

function LandingFinalCTA({ nav }) {
  return (
    <section className="cta-strip">
      <h2 className="cta-strip__h">Make your AI-ready DC capacity visible to NeevCloud IDN.</h2>
      <button className="btn btn--primary btn--lg" onClick={() => nav('/submit-capacity')}>
        Start DC Capacity Registration
      </button>
    </section>
  );
}

function Footer({ mode = 'capacity' }) {
  const { nav } = useRoute();
  const isIdn = mode === 'idn';
  return (
    <footer className="footer">
      <span>NeevCloud · Global Intelligence Delivery Network · {new Date().getFullYear()}</span>
      <span>
        {isIdn
          ? <button className="linkbtn" onClick={() => nav('/find-compute')}>Find compute</button>
          : <button className="linkbtn" onClick={() => nav('/submit-capacity')}>Register DC capacity</button>}
      </span>
      <span>{isIdn ? 'Compute discussions begin privately under the right NDA.' : 'Representative references only. No tender or commercial guarantee.'}</span>
    </footer>
  );
}

function FindCompute() {
  const { nav } = useRoute();
  const [schema, setSchema] = useState(null);
  const [sessionId, setSessionId] = useState(() => {
    try {
      return JSON.parse(localStorage.getItem(DEMAND_META_KEY) || '{}').sessionId || '';
    } catch { return ''; }
  });
  const [answers, setAnswers] = useState(() => {
    try {
      const raw = localStorage.getItem(DEMAND_STORAGE_KEY);
      return raw ? JSON.parse(raw) : {};
    } catch { return {}; }
  });
  const [status, setStatus] = useState({ state: 'loading', message: 'Loading compute selector...' });
  const [uploadState, setUploadState] = useState('');

  useEffect(() => {
    let alive = true;
    const loadDetail = async (id) => {
      if (!id) return null;
      try {
        return await apiFetch(`/api/v1/demand-intake-sessions/${id}`, { authChannel: 'demand' });
      } catch {
        return null;
      }
    };
    (async () => {
      try {
        const schemaPayload = await apiFetch('/api/v1/intake-schema/demand');
        if (!alive) return;
        setSchema(schemaPayload);

        let detail = await loadDetail(sessionId);
        if (!detail) {
          const list = await apiFetch('/api/v1/me/demand-intake-sessions', { authChannel: 'demand' }).catch(() => null);
          const latestDraft = (list?.sessions || []).find((item) => item.status === 'draft');
          detail = await loadDetail(latestDraft?.session_id);
        }
        if (detail?.session) {
          if (!alive) return;
          const loadedSessionId = detail.session.id;
          setSessionId(loadedSessionId);
          setAnswers((current) => {
            const next = Object.keys(current || {}).length ? { ...(detail.answers || {}), ...current } : (detail.answers || {});
            localStorage.setItem(DEMAND_STORAGE_KEY, JSON.stringify(next));
            return next;
          });
          localStorage.setItem(DEMAND_META_KEY, JSON.stringify({ sessionId: loadedSessionId }));
          setStatus({ state: 'idle', message: '' });
          return;
        }

        const session = await apiFetch('/api/v1/demand-intake-sessions', {
          method: 'POST',
          authChannel: 'demand',
          body: JSON.stringify({ channel: 'web_form' }),
        });
        if (!alive) return;
        setSessionId(session.id);
        localStorage.setItem(DEMAND_META_KEY, JSON.stringify({ sessionId: session.id }));
        setStatus({ state: 'idle', message: '' });
      } catch (err) {
        if (alive) setStatus({ state: 'error', message: err.message });
      }
    })();
    return () => { alive = false; };
  }, []);

  useEffect(() => {
    localStorage.setItem(DEMAND_STORAGE_KEY, JSON.stringify(answers));
  }, [answers]);

  const saveAnswer = async (questionKey, value) => {
    setAnswers((current) => ({ ...current, [questionKey]: value }));
    if (!sessionId) return;
    try {
      await apiFetch(`/api/v1/demand-intake-sessions/${sessionId}`, {
        method: 'PATCH',
        authChannel: 'demand',
        body: JSON.stringify({ answers: { [questionKey]: value }, answered_by: 'web_form' }),
      });
      setStatus({ state: 'saved', message: 'Saved' });
    } catch (err) {
      setStatus({ state: 'error', message: err.message });
    }
  };

  const uploadFile = async (file) => {
    if (!file || !sessionId) return;
    setUploadState('Uploading...');
    const body = new FormData();
    body.append('file_category', 'workload_documents');
    body.append('file', file);
    try {
      await apiFetch(`/api/v1/demand-intake-sessions/${sessionId}/files`, { method: 'POST', authChannel: 'demand', body });
      setUploadState(`Uploaded ${file.name}`);
    } catch (err) {
      setUploadState(err.message);
    }
  };

  const submit = async () => {
    if (!sessionId) return;
    setStatus({ state: 'submitting', message: 'Submitting request...' });
    try {
      const response = await apiFetch(`/api/v1/demand-intake-sessions/${sessionId}/submit`, {
        method: 'POST',
        authChannel: 'demand',
        body: JSON.stringify({}),
      });
      setStatus({ state: 'submitted', message: `Request received: ${response.request_reference}` });
    } catch (err) {
      const missing = err.payload?.detail?.missing_required_question_keys || [];
      setStatus({
        state: 'error',
        message: missing.length ? `Missing required fields: ${missing.join(', ')}` : err.message,
      });
    }
  };

  const fields = schema ? schema.sections.flatMap((section) => section.fields) : [];
  const fieldByKey = Object.fromEntries(fields.map((field) => [field.question_key, field]));
  const selectorKeys = [
    'demand.customer_type',
    'demand.workload_type',
    'demand.gpu_class',
    'demand.scale_needed',
    'demand.target_region',
    'demand.deployment_window',
    'demand.deployment_preference',
  ];
  const requiredKeys = schema?.required_question_keys || [];
  const completedRequired = requiredKeys.filter((key) => {
    const value = answers[key];
    return value !== undefined && value !== null && value !== '';
  }).length;

  if (status.state === 'loading') {
    return (
      <div className="page">
        <TopNav mode="idn" />
        <main className="demand-page"><div className="admin-empty">Loading compute selector...</div></main>
      </div>
    );
  }

  return (
    <div className="page">
      <TopNav mode="idn" />
      <main className="demand-page">
        <section className="demand-hero">
          <div className="kicker"><span className="dot" /> NeevCloud compute request</div>
          <h1 className="serif">Find the right AI compute path.</h1>
          <p>Pick the closest profile for your workload. NeevCloud will review region, GPU class, scale, timing, and deployment model privately.</p>
          <div className="demand-progress">
            <span>{completedRequired} of {requiredKeys.length} required fields complete</span>
            <i style={{ width: `${requiredKeys.length ? (completedRequired / requiredKeys.length) * 100 : 0}%` }} />
          </div>
        </section>

        <section className="demand-selector">
          {selectorKeys.map((questionKey, index) => {
            const field = fieldByKey[questionKey];
            if (!field) return null;
            return (
              <article className="demand-card" key={questionKey}>
                <div className="demand-card__head">
                  <span className="mono">{String(index + 1).padStart(2, '0')}</span>
                  <h2>{field.label}</h2>
                </div>
                <div className="demand-options">
                  {field.options.map((option) => (
                    <button
                      type="button"
                      key={option.value}
                      className={`demand-option ${answers[questionKey] === option.value ? 'demand-option--on' : ''}`}
                      onClick={() => saveAnswer(questionKey, option.value)}
                    >
                      {option.label}
                    </button>
                  ))}
                </div>
              </article>
            );
          })}
        </section>

        <section className="demand-form card">
          <div className="section__rule"><span>Contact</span><span>Required</span></div>
          <div className="demand-form__grid">
            {['contact.company_name', 'contact.full_name', 'contact.email', 'contact.phone_or_whatsapp'].map((questionKey) => {
              const field = fieldByKey[questionKey];
              return (
                <label key={questionKey}>
                  <span>{field?.label || questionKey}</span>
                  <input
                    className="input"
                    type={field?.field_type === 'email' ? 'email' : 'text'}
                    value={answers[questionKey] || ''}
                    onChange={(event) => saveAnswer(questionKey, event.target.value)}
                  />
                </label>
              );
            })}
            <label className="demand-form__wide">
              <span>{fieldByKey['demand.notes']?.label || 'Anything NeevCloud should know?'}</span>
              <textarea
                className="input"
                value={answers['demand.notes'] || ''}
                onChange={(event) => saveAnswer('demand.notes', event.target.value)}
              />
            </label>
          </div>
        </section>

        <section className="demand-form card">
          <div className="section__rule"><span>Optional context</span><span>Helpful if ready</span></div>
          <div className="demand-form__grid">
            {[
              'demand.preferred_location',
              'demand.expected_duration',
              'demand.compliance_data_residency',
              'demand.model_workload_description',
            ].map((questionKey) => {
              const field = fieldByKey[questionKey];
              const isLong = field?.field_type === 'textarea';
              return (
                <label key={questionKey} className={isLong ? 'demand-form__wide' : ''}>
                  <span>{field?.label || questionKey}</span>
                  {isLong ? (
                    <textarea className="input" value={answers[questionKey] || ''} onChange={(event) => saveAnswer(questionKey, event.target.value)} />
                  ) : (
                    <input className="input" value={answers[questionKey] || ''} onChange={(event) => saveAnswer(questionKey, event.target.value)} />
                  )}
                </label>
              );
            })}
            <label className="demand-upload demand-form__wide">
              <span>Architecture brief or workload document</span>
              <input type="file" onChange={(event) => uploadFile(event.target.files?.[0])} />
              <small>{uploadState || 'Optional PDF, document, spreadsheet, image, or ZIP.'}</small>
            </label>
          </div>
        </section>

        <section className="demand-submit">
          <div>
            <h2 className="serif">Ready for NeevCloud review?</h2>
            <p>No pricing or live availability is shown here. A NeevCloud reviewer will decide the right next step.</p>
            {status.message && <div className={`admin-status admin-status--${status.state}`}>{status.message}</div>}
          </div>
          <div className="demand-submit__actions">
            <button className="btn btn--ghost btn--lg" onClick={() => nav('/idn')}>Back to IDN</button>
            <button className="btn btn--compute btn--lg" onClick={submit} disabled={status.state === 'submitted' || status.state === 'submitting'}>
              {status.state === 'submitted' ? 'Request received' : 'Submit compute request'}
            </button>
          </div>
        </section>
        <Footer mode="idn" />
      </main>
    </div>
  );
}

function IDNHome() {
  const { nav } = useRoute();
  const openSupport = () => { window.location.href = 'https://www.neevcloud.com/support.php'; };
  return (
    <div className="page">
      <TopNav mode="idn" />
      <main className="idn">
        <section className="idn-hero" id="atlas">
          <div className="idn-hero__head">
            <div className="idn-hero__copy">
              <div className="kicker"><span className="dot" /> Global Intelligence Delivery Network</div>
              <h1 className="idn-hero__h1">NeevCloud <em>IDN</em></h1>
              <p className="idn-hero__sub">Global Intelligence Delivery Network for AI inference capacity.</p>
              <p className="idn-hero__lede">
                NeevCloud helps AI teams find regional GPU capacity for inference, private model serving, and expansion into growth markets.
              </p>
              <div className="idn-hero__cta">
                <button className="btn btn--compute btn--lg" onClick={() => nav('/find-compute')}>Find compute</button>
                <button className="btn btn--ghost btn--lg" onClick={openSupport}>Talk to NeevCloud</button>
              </div>
            </div>
            <ul className="idn-hero__stats">
              {IDN_HOME_DATA.stats.map((stat) => (
                <li key={stat.k}>
                  <span className="idn-hero__stat-v serif">{stat.v}{stat.unit && <span className="idn-hero__stat-u">{stat.unit}</span>}</span>
                  <span className="idn-hero__stat-k">{stat.k}</span>
                  <span className="idn-hero__stat-n mono">{stat.note}</span>
                </li>
              ))}
            </ul>
          </div>
          <div className="idn-hero__globe-wrap">
            <div className="idn-hero__globe">
              <IDNGlobe fallback={<IDNAtlas />} />
            </div>
          </div>
        </section>

        <section className="section" id="who">
          <div className="section__rule"><span>Who IDN is for</span><span>Demand-side compute buyers</span></div>
          <div className="section__head">
            <div><h2 className="h-title">Regional AI compute for teams that need to deploy before capacity becomes the bottleneck.</h2></div>
            <p>NeevCloud IDN is built for buyers of inference capacity: AI labs, enterprises, neoclouds, and product teams with regional demand.</p>
          </div>
          <AudienceSwitcher onCta={() => nav('/find-compute')} />
        </section>

        <section className="section" id="how">
          <div className="section__rule"><span>How IDN works</span><span>Demand intake to regional rollout</span></div>
          <div className="section__head">
            <div><h2 className="h-title">From a demand brief to live regional inference, in five steps.</h2></div>
            <p>NeevCloud captures the compute requirement, checks regional feasibility, opens technical review under NDA, and turns the brief into a deployment plan.</p>
          </div>
          <Pipeline />
        </section>

        <section className="section" id="capacity">
          <div className="section__rule"><span>Compute options</span><span>What you can ask for</span></div>
          <div className="section__head">
            <div><h2 className="h-title">Different AI workloads need different compute paths.</h2></div>
            <p>NeevCloud frames the deployment around your use case: latency, GPU class, privacy, region, scale, and launch timing.</p>
          </div>
          <CapacityTypes />
        </section>

        <section className="idn-cta idn-cta--single">
          <div className="idn-cta__card idn-cta__card--demand">
            <div className="kicker kicker--mint"><span className="dot" /> AI demand</div>
            <h3 className="idn-cta__h serif">Tell us where you need capacity.</h3>
            <p className="idn-cta__b">Region, GPU class, deployment window, compliance needs, and workload profile. NeevCloud will review the best deployment path.</p>
            <button className="btn btn--compute btn--lg" onClick={() => nav('/find-compute')}>Find compute</button>
          </div>
        </section>
        <Footer mode="idn" />
      </main>
    </div>
  );
}

function IDNGlobe({ fallback }) {
  const mountRef = useRef(null);
  const [failed, setFailed] = useState(false);

  useEffect(() => {
    const mount = mountRef.current;
    const THREE_LIB = window.THREE;
    if (!mount || !THREE_LIB || !THREE_LIB.OrbitControls) {
      setFailed(true);
      return undefined;
    }

    let disposed = false;
    let raf = null;
    const cleanup = [];
    let width = mount.clientWidth || 600;
    let height = mount.clientHeight || 600;

    const renderer = new THREE_LIB.WebGLRenderer({ antialias: true, alpha: true });
    renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
    renderer.setSize(width, height);
    renderer.setClearColor(0x000000, 0);
    if ('outputColorSpace' in renderer) renderer.outputColorSpace = THREE_LIB.SRGBColorSpace;
    renderer.domElement.style.display = 'block';
    renderer.domElement.style.cursor = 'grab';
    mount.appendChild(renderer.domElement);

    const scene = new THREE_LIB.Scene();
    const camera = new THREE_LIB.PerspectiveCamera(32, width / height, 0.1, 100);
    camera.position.set(0, 0.15, 3.9);

    const globeGroup = new THREE_LIB.Group();
    scene.add(globeGroup);

    const radius = 1;
    const degree = Math.PI / 180;
    const toVector = (lat, lon, r) => {
      const phi = (90 - lat) * degree;
      const theta = (lon + 180) * degree;
      return new THREE_LIB.Vector3(
        -r * Math.sin(phi) * Math.cos(theta),
        r * Math.cos(phi),
        r * Math.sin(phi) * Math.sin(theta),
      );
    };

    const makeGlowTexture = () => {
      const canvas = document.createElement('canvas');
      canvas.width = 128;
      canvas.height = 128;
      const ctx = canvas.getContext('2d');
      const gradient = ctx.createRadialGradient(64, 64, 0, 64, 64, 64);
      gradient.addColorStop(0, 'rgba(220,255,150,1)');
      gradient.addColorStop(0.25, 'rgba(194,240,76,0.85)');
      gradient.addColorStop(0.6, 'rgba(150,210,40,0.25)');
      gradient.addColorStop(1, 'rgba(150,210,40,0)');
      ctx.fillStyle = gradient;
      ctx.fillRect(0, 0, 128, 128);
      const texture = new THREE_LIB.CanvasTexture(canvas);
      if ('colorSpace' in texture) texture.colorSpace = THREE_LIB.SRGBColorSpace;
      cleanup.push(() => texture.dispose());
      return texture;
    };

    const globeGeometry = new THREE_LIB.SphereGeometry(radius, 96, 96);
    const markerGeometry = new THREE_LIB.SphereGeometry(0.012, 14, 14);
    const ringGeometry = new THREE_LIB.RingGeometry(0.016, 0.026, 28);
    const glowTexture = makeGlowTexture();
    const markerMaterials = {
      1: new THREE_LIB.MeshBasicMaterial({ color: 0xcdf85a }),
      2: new THREE_LIB.MeshBasicMaterial({ color: 0x8fc50e }),
      3: new THREE_LIB.MeshBasicMaterial({ color: 0x6f8a3a }),
    };
    const markers = [];
    cleanup.push(
      () => globeGeometry.dispose(),
      () => markerGeometry.dispose(),
      () => ringGeometry.dispose(),
      ...Object.values(markerMaterials).map((material) => () => material.dispose()),
    );

    new THREE_LIB.TextureLoader().load(
      'assets/globe-land.png',
      (texture) => {
        if (disposed) {
          texture.dispose();
          return;
        }
        if ('colorSpace' in texture) texture.colorSpace = THREE_LIB.SRGBColorSpace;
        texture.anisotropy = renderer.capabilities.getMaxAnisotropy ? renderer.capabilities.getMaxAnisotropy() : 1;
        cleanup.push(() => texture.dispose());
        const globeMaterial = new THREE_LIB.MeshBasicMaterial({ map: texture });
        cleanup.push(() => globeMaterial.dispose());
        globeGroup.add(new THREE_LIB.Mesh(globeGeometry, globeMaterial));
      },
      undefined,
      () => {
        if (!disposed) setFailed(true);
      },
    );

    DESIGN_MAP_NODES.forEach((node, index) => {
      const lon = (node.x / 100) * 360 - 180;
      const lat = 90 - (node.y / 100) * 180;
      const position = toVector(lat, lon, radius + 0.004);
      const tier = node.tier || 2;

      const dot = new THREE_LIB.Mesh(markerGeometry, markerMaterials[tier] || markerMaterials[2]);
      dot.position.copy(position);
      dot.scale.setScalar(tier === 1 ? 1 : 0.8);
      globeGroup.add(dot);

      const spriteMaterial = new THREE_LIB.SpriteMaterial({
        map: glowTexture,
        color: 0xffffff,
        transparent: true,
        blending: THREE_LIB.NormalBlending,
        depthWrite: false,
        opacity: tier === 1 ? 0.85 : 0.52,
      });
      cleanup.push(() => spriteMaterial.dispose());
      const sprite = new THREE_LIB.Sprite(spriteMaterial);
      sprite.position.copy(position);
      sprite.scale.setScalar(tier === 1 ? 0.085 : 0.06);
      globeGroup.add(sprite);

      if (tier === 1) {
        const ringMaterial = new THREE_LIB.MeshBasicMaterial({
          color: 0xbfe86a,
          transparent: true,
          side: THREE_LIB.DoubleSide,
          depthWrite: false,
        });
        cleanup.push(() => ringMaterial.dispose());
        const ring = new THREE_LIB.Mesh(ringGeometry, ringMaterial);
        ring.position.copy(position);
        ring.lookAt(position.clone().multiplyScalar(2));
        globeGroup.add(ring);
        markers.push({ ring, sprite, phase: (index % 6) / 6 });
      }
    });

    globeGroup.rotation.y = -0.5;
    globeGroup.rotation.x = 0.12;

    const controls = new THREE_LIB.OrbitControls(camera, renderer.domElement);
    controls.enableDamping = true;
    controls.dampingFactor = 0.08;
    controls.enablePan = false;
    controls.enableZoom = false;
    controls.rotateSpeed = 0.6;
    controls.autoRotate = true;
    controls.autoRotateSpeed = 0.5;

    const onPointerDown = () => { renderer.domElement.style.cursor = 'grabbing'; };
    const onPointerUp = () => { renderer.domElement.style.cursor = 'grab'; };
    renderer.domElement.addEventListener('pointerdown', onPointerDown);
    window.addEventListener('pointerup', onPointerUp);

    const clock = new THREE_LIB.Clock();
    const tick = () => {
      const elapsed = clock.getElapsedTime();
      markers.forEach((marker) => {
        const progress = (elapsed * 0.55 + marker.phase) % 1;
        marker.ring.scale.setScalar(1 + progress * 2.6);
        marker.ring.material.opacity = 0.7 * (1 - progress);
        marker.sprite.material.opacity = 0.75 + 0.25 * Math.sin(elapsed * 2 + marker.phase * 6);
      });
      controls.update();
      renderer.render(scene, camera);
      raf = window.requestAnimationFrame(tick);
    };
    tick();

    const resizeObserver = new ResizeObserver(() => {
      width = mount.clientWidth || width;
      height = mount.clientHeight || height;
      if (!width || !height) return;
      camera.aspect = width / height;
      camera.updateProjectionMatrix();
      renderer.setSize(width, height);
    });
    resizeObserver.observe(mount);

    return () => {
      disposed = true;
      if (raf) window.cancelAnimationFrame(raf);
      resizeObserver.disconnect();
      renderer.domElement.removeEventListener('pointerdown', onPointerDown);
      window.removeEventListener('pointerup', onPointerUp);
      controls.dispose();
      cleanup.forEach((dispose) => dispose());
      renderer.dispose();
      if (renderer.domElement.parentNode) renderer.domElement.parentNode.removeChild(renderer.domElement);
    };
  }, []);

  if (failed) {
    return <div className="idn-globe idn-globe--fallback">{fallback}</div>;
  }
  return <div className="idn-globe" ref={mountRef} aria-label="Interactive 3D globe of representative NeevCloud IDN capacity markets" />;
}

function IDNAtlas() {
  const nodes = DESIGN_MAP_NODES;
  const [tier, setTier] = useState('all');
  const [showDemand, setShowDemand] = useState(true);
  const [active, setActive] = useState('nl-ams');
  const selected = nodes.find((node) => node.id === active);
  const demandById = Object.fromEntries(IDN_HOME_DATA.demand.map((item) => [item.id, item]));
  const supplyById = Object.fromEntries(nodes.map((item) => [item.id, item]));
  const arcs = [
    ['us-va', 'd-na'], ['us-sv', 'd-na'], ['gb-lon', 'd-eu'], ['nl-ams', 'd-eu'],
    ['ae-auh', 'd-eu'], ['in-bom', 'd-in'], ['sg-sin', 'd-ap'], ['jp-tyo', 'd-ap'],
  ];
  const tierOptions = [['all', 'All tiers'], [1, 'Tier 1'], [2, 'Tier 2'], [3, 'Tier 3']];
  const visibleNodes = nodes.filter((node) => tier === 'all' || node.tier === tier);
  return (
    <div className="idn-map">
      <div className="idn-map__filters">
        <div className="idn-filter">
          <span className="idn-filter__label">Tier</span>
          <span className="idn-filter__chips">
            {tierOptions.map(([value, label]) => (
              <button key={value} className={`idn-chip ${tier === value ? 'idn-chip--on' : ''}`} onClick={() => setTier(value)}>{label}</button>
            ))}
          </span>
        </div>
        <div className="idn-filter idn-filter--toggle">
          <span className="idn-filter__label">Demand</span>
          <span className="idn-filter__chips">
            <button className={`idn-chip ${showDemand ? 'idn-chip--on' : ''}`} onClick={() => setShowDemand((value) => !value)}>Briefs</button>
          </span>
        </div>
      </div>
      <div className="idn-map__stage">
        <img className="idn-map__base" src="assets/world-map.svg" alt="" aria-hidden="true" />
        <svg className="idn-map__arcs" viewBox="0 0 100 100" preserveAspectRatio="none" aria-hidden="true">
          {showDemand && arcs.map(([from, to]) => {
            const a = supplyById[from];
            const b = demandById[to];
            if (!a || !b) return null;
            const midX = (a.x + b.x) / 2;
            const midY = Math.min(a.y, b.y) - 8;
            return <path key={`${from}-${to}`} className="idn-arc" d={`M ${a.x} ${a.y} Q ${midX} ${midY} ${b.x} ${b.y}`} />;
          })}
        </svg>
        <div className="idn-map__overlay">
          {showDemand && IDN_HOME_DATA.demand.map((item) => (
            <span key={item.id} className="idn-demand" style={{ left: `${item.x}%`, top: `${item.y}%` }} title={item.label}>
              <span className="idn-demand__ring" />
            </span>
          ))}
          {nodes.map((node) => {
            const visible = visibleNodes.includes(node);
            return (
              <button
                type="button"
                key={node.id}
                className={`idn-node idn-node--t${node.tier}${node.liquid ? ' idn-node--liquid' : ''}${active === node.id ? ' idn-node--active' : ''}`}
                style={{ left: `${node.x}%`, top: `${node.y}%`, opacity: visible ? 1 : 0.14, pointerEvents: visible ? 'auto' : 'none' }}
                onClick={() => setActive(node.id)}
                aria-label={`${node.city}, ${node.country}`}
              >
                <span className="idn-node__pulse" />
                <span className="idn-node__dot" />
              </button>
            );
          })}
        </div>
        <div className="idn-map__legend">
          <span><i className="t1" /> Tier 1</span>
          <span><i className="t2" /> Tier 2</span>
          <span><i className="t3" /> Tier 3</span>
          <span><i className="dem" /> Demand brief</span>
        </div>
        <div className="idn-map__count"><strong>{visibleNodes.length}</strong> representative nodes</div>
        {selected && (
          <div className="idn-map__detail">
            <div className="idn-map__detail__top">
              <span className="idn-map__detail__country">{selected.country}</span>
              <span className={`idn-tierbadge idn-tierbadge--t${selected.tier}`}>Tier {selected.tier}</span>
            </div>
            <h4>{selected.city}</h4>
            <div className="idn-map__detail__rows">
              <div><span>Cooling</span><b>{selected.liquid ? 'Liquid-ready signal' : 'Air or unspecified'}</b></div>
              <div><span>Role</span><b>Representative supply marker</b></div>
              <div><span>Disclosure</span><b>Public map reference</b></div>
            </div>
            <div className="idn-map__detail__hidden">No live capacity, partnership, or reservation implied.</div>
          </div>
        )}
      </div>
    </div>
  );
}

function AudienceSwitcher({ onCta }) {
  const [index, setIndex] = useState(0);
  const audience = IDN_HOME_DATA.audiences[index];
  return (
    <div className="aud">
      <div className="aud__tabs" role="tablist">
        {IDN_HOME_DATA.audiences.map((item, itemIndex) => (
          <button
            key={item.id}
            role="tab"
            aria-selected={itemIndex === index}
            className={`aud__tab ${itemIndex === index ? 'aud__tab--on' : ''}`}
            onClick={() => setIndex(itemIndex)}
          >
            {item.tab}
          </button>
        ))}
      </div>
      <div className="aud__panel" key={audience.id}>
        <div className="aud__copy">
          <div className="kicker kicker--mint">
            <span className="dot" /> {audience.kicker}
          </div>
          <h3 className="aud__h serif">{audience.h}</h3>
          <p className="aud__b">{audience.b}</p>
          <button
            className="btn btn--compute"
            onClick={() => onCta(audience)}
          >
            {audience.cta}
          </button>
        </div>
        <ul className="aud__points">
          {audience.points.map((point, pointIndex) => (
            <li key={point}><span className="aud__pt-n">{String(pointIndex + 1).padStart(2, '0')}</span>{point}</li>
          ))}
        </ul>
      </div>
    </div>
  );
}

function Pipeline() {
  const [active, setActive] = useState(0);
  const step = IDN_HOME_DATA.pipeline[active];
  return (
    <div className="pipe">
      <div className="pipe__track">
        {IDN_HOME_DATA.pipeline.map((item, index) => (
          <button
            key={item.n}
            className={`pipe__step ${index === active ? 'pipe__step--on' : ''} ${index < active ? 'pipe__step--done' : ''}`}
            onClick={() => setActive(index)}
          >
            <span className="pipe__node"><span className="pipe__node-dot" /></span>
            <span className="pipe__n">{item.n}</span>
            <span className="pipe__k">{item.k}</span>
            <span className="pipe__side">{item.side}</span>
          </button>
        ))}
      </div>
      <div className="pipe__detail" key={active}>
        <div className="pipe__detail__n serif">{step.n}</div>
        <div>
          <h4 className="pipe__detail__k serif">{step.k}</h4>
          <p className="pipe__detail__b">{step.b}</p>
        </div>
        <div className="pipe__detail__nav">
          <button className="iconbtn" onClick={() => setActive((value) => Math.max(0, value - 1))} disabled={active === 0} aria-label="Previous">←</button>
          <button className="iconbtn" onClick={() => setActive((value) => Math.min(IDN_HOME_DATA.pipeline.length - 1, value + 1))} disabled={active === IDN_HOME_DATA.pipeline.length - 1} aria-label="Next">→</button>
        </div>
      </div>
    </div>
  );
}

function CapacityTypes() {
  return (
    <div className="captypes">
      {IDN_HOME_DATA.capacityTypes.map((item, index) => (
        <article className="captype card card--hover" key={item.k}>
          <div className="captype__n mono">{String(index + 1).padStart(2, '0')}</div>
          <h4 className="captype__k serif">{item.k}</h4>
          <p className="captype__b">{item.b}</p>
          <div className="captype__spec">
            {item.spec.map((spec) => <span className="tag" key={spec}>{spec}</span>)}
          </div>
        </article>
      ))}
    </div>
  );
}

// =========================================================================
// SUPPLY FORM
// =========================================================================
const SECTIONS = [
  { id: 'docs', n: '01', label: 'Upload documents', mins: 3 },
  { id: 'company', n: '02', label: 'Company & contact', mins: 3 },
  { id: 'facility', n: '03', label: 'Facility identity', mins: 3 },
  { id: 'power', n: '04', label: 'Power readiness', mins: 3 },
  { id: 'cooling', n: '05', label: 'NVIDIA-class readiness', mins: 3 },
  { id: 'commercial', n: '06', label: 'Commercial availability', mins: 2 },
  { id: 'review', n: '07', label: 'Review & submit', mins: 2 },
];

function SupplyForm() {
  const { nav } = useRoute();
  const {
    answers,
    setAnswer,
    setBackendAnswer,
    savedAt,
    apiStatus,
    schemaStatus,
    formSchema,
    questionIndex,
    documentStatus,
    prefillReview,
    prefillStatus,
    uploadFile,
    uploadArtifact,
    extractDocuments,
    acceptPrefillAnswer,
    rejectPrefillAnswer,
    submit,
    getVoiceState,
    getVoiceToken,
    registerVoiceConversation,
    proposeVoiceAnswer,
    confirmVoiceOverwrite,
    markVoiceUnresolved,
    prepareVoiceReview,
    resetVoiceIntake,
    attachArtifactLink,
    backendKeyMap,
    fileQuestionMap,
    questionSectionMap,
    normalizeBackendAnswer,
  } = useForm();
  const [active, setActive] = useState(SECTIONS[0].id);
  const [showResume, setShowResume] = useState(false);
  const [submitError, setSubmitError] = useState('');
  const [voiceCollapsed, setVoiceCollapsed] = useState(window.NEEV_TWEAKS_DEFAULTS?.voicePanel === 'collapsed');
  useEffect(() => {
    const onTweak = (e) => {
      const edits = e.detail || {};
      if ('voicePanel' in edits) setVoiceCollapsed(edits.voicePanel === 'collapsed');
    };
    window.addEventListener('tweakchange', onTweak);
    return () => window.removeEventListener('tweakchange', onTweak);
  }, []);

  const completion = useMemo(() => computeCompletion(answers, questionIndex), [answers, questionIndex]);

  const goToSection = (id) => {
    setActive(id);
    document.getElementById(`sec-${id}`)?.scrollIntoView({ block: 'start', behavior: 'smooth' });
  };

  // Track which section is in view via scroll
  const formRef = useRef(null);
  useEffect(() => {
    const el = formRef.current;
    if (!el) return;
    const onScroll = () => {
      const sections = SECTIONS.map(s => document.getElementById(`sec-${s.id}`)).filter(Boolean);
      const top = el.scrollTop + 120;
      let cur = SECTIONS[0].id;
      for (const s of sections) {
        if (s.offsetTop <= top) cur = s.id.replace('sec-', '').replace('sec_', '');
      }
      // simpler: pick last whose offsetTop <= top
      let last = SECTIONS[0].id;
      sections.forEach(s => {
        if (s.offsetTop - el.offsetTop <= top - el.offsetTop) last = s.id.slice(4);
      });
      setActive(last);
    };
    el.addEventListener('scroll', onScroll);
    return () => el.removeEventListener('scroll', onScroll);
  }, []);

  const idx = SECTIONS.findIndex(s => s.id === active);
  const next = () => {
    if (idx < SECTIONS.length - 1) goToSection(SECTIONS[idx + 1].id);
  };
  const prev = () => {
    if (idx > 0) goToSection(SECTIONS[idx - 1].id);
  };

  const submitForm = async () => {
    setSubmitError('');
    try {
      await submit();
      nav('/submit-capacity/thank-you');
    } catch (err) {
      const missing = err?.payload?.detail?.missing_required_question_keys;
      const suffix = Array.isArray(missing) && missing.length ? ` Missing: ${missing.join(', ')}` : '';
      setSubmitError(`${err.message}${suffix}`);
    }
  };

  const requiredKeys = questionIndex.requiredLocalKeys?.length ? questionIndex.requiredLocalKeys : REQUIRED_LOCAL_KEYS;
  const requiredFilled = requiredKeys.filter(k => {
    const v = answers[k];
    if (Array.isArray(v)) return v.length > 0;
    return v !== undefined && v !== null && v !== '';
  }).length;

  return (
    <VoiceProvider
      setAnswer={setAnswer}
      setBackendAnswer={setBackendAnswer}
      answers={answers}
      scrollToSection={goToSection}
      getVoiceState={getVoiceState}
      getVoiceToken={getVoiceToken}
      registerVoiceConversation={registerVoiceConversation}
      proposeVoiceAnswer={proposeVoiceAnswer}
      confirmVoiceOverwrite={confirmVoiceOverwrite}
      markVoiceUnresolved={markVoiceUnresolved}
      prepareVoiceReview={prepareVoiceReview}
      resetVoiceIntake={resetVoiceIntake}
      uploadArtifact={uploadArtifact}
      attachArtifactLink={attachArtifactLink}
      backendKeyMap={backendKeyMap}
      fileQuestionMap={fileQuestionMap}
      questionSectionMap={questionSectionMap}
      normalizeBackendAnswer={normalizeBackendAnswer}
      validateBackendAnswer={(questionKey, value) => validateBackendAnswerValue(questionKey, value, questionIndex)}
      formSchema={formSchema}
      questionIndex={questionIndex}
    >
      <div className={`form-page form-page--with-voice ${voiceCollapsed ? 'form-page--voice-collapsed' : ''}`}>
        <FormHeader
          completion={completion}
          savedAt={savedAt}
          apiStatus={apiStatus}
          onResume={() => setShowResume(true)}
          onVoice={() => setVoiceCollapsed(false)}
          onExit={() => nav('/')}
        />
        <div className="form-shell">
          <FormRail active={active} onPick={goToSection} answers={answers} questionIndex={questionIndex} />
          <main className="form-main" ref={formRef}>
            <FormIntro />
            {SECTIONS.map((sec, i) => (
              <FormSection
                key={sec.id}
                section={sec}
                isLast={i === SECTIONS.length - 1}
                answers={answers}
                setAnswer={setAnswer}
                uploadFile={uploadFile}
                documentStatus={documentStatus}
                prefillReview={prefillReview}
                prefillStatus={prefillStatus}
                extractDocuments={extractDocuments}
                acceptPrefillAnswer={acceptPrefillAnswer}
                rejectPrefillAnswer={rejectPrefillAnswer}
                apiStatus={apiStatus}
                onNext={next}
                onPrev={prev}
                onSubmit={submitForm}
                submitError={submitError}
                completion={completion}
                questionIndex={questionIndex}
                schemaStatus={schemaStatus}
              />
            ))}
            <div style={{ height: '40vh' }} />
          </main>
          <VoicePanel
            completion={completion}
            requiredCount={requiredKeys.length}
            requiredFilled={requiredFilled}
            collapsed={voiceCollapsed}
            onCollapse={() => setVoiceCollapsed(c => !c)}
          />
        </div>
        {showResume && <ResumeModal onClose={() => setShowResume(false)} />}
      </div>
    </VoiceProvider>
  );
}

function VoiceDemoDriver({ setAnswer }) {
  const v = useVoice();
  const lastDemo = useRef(null);
  const lastPanel = useRef(null);
  const apply = useCallback((demo) => {
    if (!demo || lastDemo.current === demo) return;
    lastDemo.current = demo;
    v.setPendingConfirm(null);
    v.setFollowUps([]);
    v.setNeedsConfirm([]);
    if (demo === 'idle') {
      v.pause();
    } else if (demo === 'listening') {
      v.start();
      v.pushTranscript('agent', 'How many megawatts is the total sanctioned site power?');
      v.pushTranscript('user', 'About 40 megawatts total, around 12 vacant right now.');
      setAnswer('total_site_power_mw', 40);
      v.setActivity([{ key: 'total_site_power_mw', label: 'Total site power saved', value: '40 MW', kind: 'saved', ts: Date.now() }]);
      v.setPhase('listening');
    } else if (demo === 'speaking') {
      v.start();
      v.pushTranscript('agent', 'What is the substation or feeder detail?');
      v.setPhase('speaking');
    } else if (demo === 'thinking') {
      v.start();
      v.setPhase('thinking');
    } else if (demo === 'saving') {
      v.start();
      v.flashField('substation_feeder_detail');
      setAnswer('substation_feeder_detail', '220 kV feeder, N+1 redundancy');
      v.setActivity([{ key: 'substation_feeder_detail', label: 'Substation detail saved', value: '220 kV feeder, N+1 redundancy', kind: 'saved', ts: Date.now() }]);
      v.setPhase('saving');
      setTimeout(() => v.setPhase('listening'), 1400);
    } else if (demo === 'confirm') {
      v.start();
      setAnswer('vacant_it_load_mw', 10);
      v.setPendingConfirm({ key: 'vacant_it_load_mw', label: 'Vacant IT load', oldValue: '10 MW', newValue: '12 MW' });
      v.setPhase('confirm');
    } else if (demo === 'disconnected') {
      v.setPhase('disconnected');
    } else if (demo === 'reviewing') {
      v.setPhase('reviewing');
    } else if (demo === 'education') {
      v.askEducation('NeevCloud IDN Level 1 review', 'This registration creates a site profile for NeevCloud IDN review. Qualified sites can be contacted when relevant regional tenders are released.');
    }
  }, [v, setAnswer]);

  // Initial apply from defaults
  useEffect(() => {
    apply(window.NEEV_TWEAKS_DEFAULTS?.voiceDemo);
  }, [apply]);

  // Subscribe to tweakchange events so we react regardless of which component owns the tweaks state
  useEffect(() => {
    const onTweak = (e) => {
      const edits = e.detail || {};
      if ('voiceDemo' in edits) apply(edits.voiceDemo);
      if ('voicePanel' in edits) {
        lastPanel.current = edits.voicePanel;
        window.dispatchEvent(new CustomEvent('voicepanelchange', { detail: edits.voicePanel }));
      }
    };
    window.addEventListener('tweakchange', onTweak);
    return () => window.removeEventListener('tweakchange', onTweak);
  }, [apply]);
  return null;
}

function computeCompletion(answers, questionIndex = EMPTY_QUESTION_INDEX) {
  const requiredKeys = questionIndex.requiredLocalKeys?.length ? questionIndex.requiredLocalKeys : REQUIRED_LOCAL_KEYS;
  const filled = requiredKeys.filter(k => {
    const v = answers[k];
    if (Array.isArray(v)) return v.length > 0;
    return v !== undefined && v !== null && v !== '';
  }).length;
  return Math.round((filled / Math.max(1, requiredKeys.length)) * 100);
}

function FormHeader({ completion, savedAt, apiStatus, onResume, onVoice, onExit }) {
  const saved = apiStatus?.message || (savedAt ? `Saved ${timeAgo(savedAt)}` : 'Autosave on');
  return (
    <header className="formhdr">
      <div className="formhdr__l">
        <button className="iconbtn" onClick={onExit} aria-label="Back to landing">
          <svg width="14" height="14" viewBox="0 0 14 14"><path d="M9 2L4 7L9 12" stroke="currentColor" strokeWidth="1.5" fill="none"/></svg>
        </button>
        <div className="brand brand--sm">
          <span className="brand__logo-shell" aria-hidden="true"><img className="brand__logo" src={NEEVCLOUD_LOGO_SRC} alt="" /></span>
          <span className="brand__sep">/</span>
          <span className="brand__sub">Level 1 qualification</span>
        </div>
      </div>
      <div className="formhdr__c">
        <div className="formhdr__progress">
          <div className="formhdr__bar"><div className="formhdr__fill" style={{ width: `${completion}%` }} /></div>
          <div className="formhdr__pct"><b>{completion}%</b> complete</div>
        </div>
      </div>
      <div className="formhdr__r">
        <span className={`formhdr__saved ${apiStatus?.state === 'error' ? 'formhdr__saved--error' : ''}`}><span className="pulse-dot" /> {saved}</span>
        <button className="formhdr__voice btn btn--ghost btn--sm" onClick={onVoice}>
          <span className="formhdr__voice-shimmer" aria-hidden="true" />
          <svg className="formhdr__voice-mic" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
            <rect x="9" y="2" width="6" height="12" rx="3" />
            <path d="M19 11a7 7 0 0 1-14 0" />
            <line x1="12" y1="18" x2="12" y2="22" />
            <line x1="8" y1="22" x2="16" y2="22" />
          </svg>
          <span className="formhdr__voice-label">AI Voice Survey</span>
        </button>
        <button className="btn btn--ghost btn--sm" onClick={onResume}>Save &amp; continue</button>
      </div>
    </header>
  );
}

function timeAgo(d) {
  if (!d) return '';
  const s = Math.round((Date.now() - d.getTime()) / 1000);
  if (s < 5) return 'just now';
  if (s < 60) return `${s}s ago`;
  const m = Math.round(s / 60);
  return `${m}m ago`;
}

function FormRail({ active, onPick, answers, questionIndex }) {
  return (
    <aside className="rail" aria-label="Form sections">
      <div className="rail__hd serif">Level 1 qualification</div>
      <div className="rail__sub">7 sections · short review</div>
      <ol className="rail__list">
        {SECTIONS.map((s) => {
          const status = sectionStatus(s.id, answers, questionIndex);
          return (
            <li key={s.id}>
              <button
                className={`rail__item ${active === s.id ? 'rail__item--active' : ''}`}
                onClick={() => onPick(s.id)}
              >
                <span className={`rail__bullet rail__bullet--${status}`} aria-hidden="true">
                  {status === 'done' ? '✓' : s.n}
                </span>
                <span className="rail__lbl">{s.label}</span>
                <span className="rail__mins">{s.mins}m</span>
              </button>
            </li>
          );
        })}
      </ol>
      <div className="rail__hint">
        <div className="rail__hint-hd">AI-ready capacity review</div>
        <p>Upload site documents first. NeevCloud classifies evidence, pre-fills Level 1 answers, and reviews your site for future tender fit.</p>
        <button className="linkbtn">Invite a colleague</button>
      </div>
    </aside>
  );
}

function sectionStatus(id, answers, questionIndex = EMPTY_QUESTION_INDEX) {
  const map = {
    docs: [],
    company: ['company_legal_name', 'company_type', 'representative_type', 'contact_full_name', 'contact_email', 'contact_phone_or_whatsapp'],
    facility: ['facility_name', 'facility_city', 'facility_country', 'facility_stage'],
    power: ['total_site_power_mw', 'vacant_it_load_mw', 'power_available_3_6_months_mw'],
    cooling: ['earliest_deployment_start_date', 'current_rack_density_kw', 'max_rack_density_after_upgrade_kw', 'cooling_type'],
    commercial: ['willing_host_neevcloud_gpus'],
    review: [],
  };
  const keys = questionIndex.requiredBySection?.[id]?.length ? questionIndex.requiredBySection[id] : (map[id] || []);
  if (keys.length === 0) return 'todo';
  const filled = keys.filter(k => {
    const v = answers[k];
    if (Array.isArray(v)) return v.length > 0;
    return v !== undefined && v !== null && v !== '';
  }).length;
  if (filled === 0) return 'todo';
  if (filled === keys.length) return 'done';
  return 'partial';
}

function FormIntro() {
  return (
    <div className="form-intro">
      <div className="section__rule"><span>§ Intake</span><span>Begin</span></div>
      <h1 className="serif form-intro__h">Start Level 1 AI-ready capacity qualification</h1>
      <p className="form-intro__lede">
        Start with optional document upload. NeevCloud classifies uploaded files and pre-fills answers where possible; then you complete the short Level 1 form for site ownership, location, power, rack density, cooling, timing, and commercial availability.
      </p>
    </div>
  );
}

function FormSection({
  section,
  isLast,
  answers,
  setAnswer,
  uploadFile,
  documentStatus,
  prefillReview,
  prefillStatus,
  extractDocuments,
  acceptPrefillAnswer,
  rejectPrefillAnswer,
  apiStatus,
  onNext,
  onPrev,
  onSubmit,
  submitError,
  completion,
  questionIndex,
  schemaStatus,
}) {
  const schemaFields = schemaFieldsForSection(section.id, questionIndex);
  const body = section.id === 'review'
    ? <ReviewSectionContent answers={answers} apiStatus={apiStatus} onSubmit={onSubmit} submitError={submitError} completion={completion} />
    : schemaFields.length > 0
      ? (
        <SchemaSectionContent
          section={section}
          fields={schemaFields}
          answers={answers}
          setAnswer={setAnswer}
          uploadFile={uploadFile}
          documentStatus={documentStatus}
          prefillReview={prefillReview}
          prefillStatus={prefillStatus}
          extractDocuments={extractDocuments}
          acceptPrefillAnswer={acceptPrefillAnswer}
          rejectPrefillAnswer={rejectPrefillAnswer}
          questionIndex={questionIndex}
        />
      )
      : <SchemaStatusBlock status={schemaStatus} />;

  return (
    <section className="fsec" id={`sec-${section.id}`}>
      <div className="fsec__hd">
        <div className="fsec__n serif">§ {section.n}</div>
        <h2 className="fsec__h serif">{section.label}</h2>
        <div className="fsec__mins">~{section.mins} min</div>
      </div>
      <div className="fsec__body">
        {body}
      </div>
      <div className="fsec__nav">
        <button className="btn btn--ghost" onClick={onPrev} disabled={section.id === 'docs'}>
          ← Previous
        </button>
        {!isLast && (
          <button className="btn btn--primary" onClick={onNext}>
            Save & continue
          </button>
        )}
      </div>
    </section>
  );
}

// =========================================================================
// FIELD PRIMITIVES
// =========================================================================
const FIELD_LABEL_KEY_MAP = {};


function Field({ label, hint, required, children, span = 1, fieldKey }) {
  const v = (typeof useVoice === 'function') ? useVoice() : null;
  const ref = useRef(null);
  const key = fieldKey || labelToKey(label);
  useEffect(() => {
    if (v && key) v.registerField(key, ref.current);
    return () => { if (v && key) v.registerField(key, null); };
  }, [v, key]);
  const flashing = v && v.highlight === key;
  return (
    <div
      ref={ref}
      className={`field ${flashing ? 'field--ai-flash' : ''}`}
      style={{ gridColumn: `span ${span}` }}
      data-field={key}
    >
      <label className="field__lbl">
        {label}
        {required && <span className="field__req">*</span>}
        {flashing && <span className="field__ai">Saved by AI</span>}
      </label>
      {hint && <div className="field__hint">{hint}</div>}
      {children}
    </div>
  );
}

function labelToKey(s) {
  if (!s) return null;
  if (FIELD_LABEL_KEY_MAP[s]) return FIELD_LABEL_KEY_MAP[s];
  return String(s).toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_|_$/g, '');
}

function TextInput({ value, onChange, placeholder, type = 'text' }) {
  const displayValue = Array.isArray(value) ? value.join(', ') : value;
  return (
    <input
      className="input"
      type={type}
      value={displayValue || ''}
      onChange={(e) => onChange(e.target.value)}
      placeholder={placeholder}
    />
  );
}

function NumberInput({ value, onChange, suffix, placeholder }) {
  const displayValue = ['unknown', 'not_applicable'].includes(value) ? '' : value;
  return (
    <div className="input input--with-suffix">
      <input
        type="number"
        value={displayValue ?? ''}
        onChange={(e) => onChange(e.target.value === '' ? '' : Number(e.target.value))}
        placeholder={placeholder}
      />
      {suffix && <span className="input__suffix">{suffix}</span>}
    </div>
  );
}

function Textarea({ value, onChange, placeholder, rows = 3 }) {
  const displayValue = Array.isArray(value) ? value.join(', ') : value;
  return (
    <textarea
      className="input input--textarea"
      value={displayValue || ''}
      onChange={(e) => onChange(e.target.value)}
      placeholder={placeholder}
      rows={rows}
    />
  );
}

function Select({ value, onChange, options, placeholder }) {
  return (
    <div className="select">
      <select value={value || ''} onChange={(e) => onChange(e.target.value)}>
        <option value="" disabled>{placeholder || 'Select…'}</option>
        {options.map(o => <option key={o.value} value={o.value}>{o.label}</option>)}
      </select>
      <span className="select__chev">▾</span>
    </div>
  );
}

function Chips({ value, onChange, options, multi = true }) {
  const v = Array.isArray(value) ? value : (value ? [value] : []);
  const extra = v.filter((item) => !options.includes(item));
  const toggle = (o) => {
    if (multi) {
      onChange(v.includes(o) ? v.filter(x => x !== o) : [...v, o]);
    } else {
      onChange(o);
    }
  };
  return (
    <div className="chips">
      {options.map(o => (
        <button
          key={o}
          type="button"
          className={`chip ${(multi ? v.includes(o) : value === o) ? 'chip--on' : ''}`}
          onClick={() => toggle(o)}
        >
          {o}
        </button>
      ))}
      {extra.map((o) => (
        <button
          key={`extra-${o}`}
          type="button"
          className="chip chip--on chip--custom"
          onClick={() => multi ? onChange(v.filter(x => x !== o)) : onChange('')}
          title="Captured by voice or text input"
        >
          {o}
        </button>
      ))}
    </div>
  );
}

function YesNo({ value, onChange }) {
  return (
    <div className="yesno">
      {['Yes', 'No', 'Unsure'].map(o => (
        <button
          key={o}
          type="button"
          className={`chip ${value === o ? 'chip--on' : ''}`}
          onClick={() => onChange(o)}
        >
          {o}
        </button>
      ))}
    </div>
  );
}

function schemaFieldsForSection(sectionId, questionIndex = EMPTY_QUESTION_INDEX) {
  if (!questionIndex?.allQuestionKeys?.length) return [];
  return questionIndex.allQuestionKeys
    .map((questionKey) => questionIndex.fieldsByQuestion?.[questionKey])
    .filter((field) => field?.ui_section_id === sectionId);
}

function localKeyForSchemaField(field) {
  return BACKEND_KEY_MAP[field.question_key] || FILE_QUESTION_MAP[field.question_key] || null;
}

function schemaChoiceOptions(field) {
  return getSchemaOptionLabels(field);
}

function schemaIsYesNo(field) {
  const options = schemaChoiceOptions(field);
  return field?.field_type === 'select' && options.includes('Yes') && options.includes('No');
}

function schemaFieldSpan(field) {
  const options = schemaChoiceOptions(field);
  if (field.field_type === 'textarea') return 2;
  if (field.field_type === 'multiselect' || field.field_type === 'multiselect_freeform') return 2;
  if (field.field_type === 'select' && options.length > 3) return 2;
  if (field.field_type === 'file') return 1;
  if (String(field.question_key).endsWith('.legal_name')) return 2;
  return 1;
}

function schemaNumberSuffix(field) {
  const key = String(field.question_key || '');
  const label = String(field.label || '').toLowerCase();
  if (field.unit) return field.unit;
  if (key.includes('estimated_power_cost_per_kwh') || label.includes('per kwh')) return '/ kWh';
  if (key.includes('rack_density') || label.includes('kw/rack')) return 'kW / rack';
  if (key.endsWith('_mw') || label.includes(' in mw')) return 'MW';
  return '';
}

function schemaInputType(field) {
  if (field.field_type === 'phone') return 'tel';
  if (['date', 'email', 'url'].includes(field.field_type)) return field.field_type;
  return 'text';
}

function SchemaStatusBlock({ status }) {
  const message = status?.state === 'error'
    ? status.message
    : 'Loading canonical form schema...';
  return (
    <div className="callout">
      <div className="callout__hd">Canonical schema</div>
      <p>{message}</p>
    </div>
  );
}

function SchemaSectionContent({
  section,
  fields,
  answers,
  setAnswer,
  uploadFile,
  documentStatus,
  prefillReview,
  prefillStatus,
  extractDocuments,
  acceptPrefillAnswer,
  rejectPrefillAnswer,
  questionIndex,
}) {
  if (section.id === 'docs') {
    return (
      <DocumentUploadStep
        fields={fields}
        answers={answers}
        documentStatus={documentStatus}
        uploadFile={uploadFile}
      />
    );
  }

  const shouldShowPrefillReview = section.id === 'company' && (
    (documentStatus?.files || []).length > 0 ||
    (prefillReview?.needs_review || []).length > 0 ||
    (prefillReview?.conflicts || []).length > 0 ||
    (prefillReview?.auto_accepted_question_keys || []).length > 0
  );
  const uploadedFileIds = (documentStatus?.files || []).map((file) => file.id).filter(Boolean);

  return (
    <>
      {shouldShowPrefillReview && (
        <PrefillReviewPanel
          uploadedFileIds={uploadedFileIds}
          documentStatus={documentStatus}
          prefillReview={prefillReview}
          prefillStatus={prefillStatus}
          questionIndex={questionIndex}
          onAnalyze={() => extractDocuments(uploadedFileIds)}
          onAccept={acceptPrefillAnswer}
          onReject={rejectPrefillAnswer}
        />
      )}
      <div className="grid">
        {fields.map((field) => (
          <SchemaFieldControl
            key={field.question_key}
            field={field}
            answers={answers}
            setAnswer={setAnswer}
            uploadFile={uploadFile}
          />
        ))}
      </div>
    </>
  );
}

function DocumentUploadStep({ fields, answers, documentStatus, uploadFile }) {
  const inputRef = useRef(null);
  const [drag, setDrag] = useState(false);
  const [uploading, setUploading] = useState(false);
  const [error, setError] = useState('');
  const schemaField = fields?.[0] || {};
  const localFiles = answers[DEFAULT_DOCUMENT_LOCAL_KEY];
  const localList = Array.isArray(localFiles) ? localFiles : (localFiles ? [localFiles] : []);
  const backendFiles = documentStatus?.files || [];
  const files = backendFiles.length ? backendFiles.map((file) => ({
    name: file.original_filename,
    size: file.size_bytes,
    uploadedAt: file.uploaded_at,
    fileId: file.id,
    fileCategory: file.file_category,
    detectedDocumentType: file.detected_document_type,
    extractionStatus: file.extraction_status,
  })) : localList;

  const uploadFiles = async (fileList) => {
    const selected = Array.from(fileList || []);
    if (!selected.length) return;
    setUploading(true);
    setError('');
    try {
      for (const file of selected) {
        await uploadFile(DEFAULT_DOCUMENT_LOCAL_KEY, file);
      }
    } catch (err) {
      setError(err.message || 'Upload failed');
    } finally {
      setUploading(false);
    }
  };

  return (
    <div className="docs docs--single">
      <div className="callout">
        <div className="callout__hd">Upload documents first</div>
        <p>Upload whatever you already have: RFI or checklist responses, floor plans, power letters, single-line diagrams, cooling specs, carrier sheets, certifications, facility photos, brochures, ownership proof, or a ZIP bundle. NeevCloud will classify the files in the backend and use them to prefill the downstream form where possible.</p>
      </div>

      <div
        className={`doc-upload ${drag ? 'doc-upload--drag' : ''}`}
        onDragOver={(event) => { event.preventDefault(); setDrag(true); }}
        onDragLeave={() => setDrag(false)}
        onDrop={(event) => {
          event.preventDefault();
          setDrag(false);
          uploadFiles(event.dataTransfer.files);
        }}
      >
        <div className="doc-upload__icon" aria-hidden="true">
          <svg width="34" height="34" viewBox="0 0 34 34"><path d="M17 5V23M17 23L9 15M17 23L25 15M6 28H28" stroke="currentColor" strokeWidth="1.4" fill="none" strokeLinecap="round" strokeLinejoin="round"/></svg>
        </div>
        <h3 className="doc-upload__title serif">{schemaField.label || 'Upload facility documents'}</h3>
        <p className="doc-upload__copy">{schemaField.help_text || 'Upload RFI files, floor plans, photos, and any supporting documents you want NeevCloud to use for prefill.'}</p>
        <button className="btn btn--primary" type="button" onClick={() => inputRef.current?.click()} disabled={uploading}>
          {uploading ? 'Uploading...' : 'Choose documents'}
        </button>
        <div className="doc-upload__hint">PDF · DWG · PNG · JPG · ZIP · TXT · max 50 MB each</div>
        <input
          ref={inputRef}
          type="file"
          hidden
          multiple
          onChange={(event) => {
            uploadFiles(event.target.files);
            event.target.value = '';
          }}
        />
        {error && <div className="doc__error">{error}</div>}
      </div>

      {files.length > 0 && (
        <div className="doc-list" aria-label="Uploaded documents">
          <div className="doc-list__hd">
            <span>{files.length} document{files.length === 1 ? '' : 's'} uploaded</span>
            <span>Classification happens after upload</span>
          </div>
          {files.map((file, index) => (
            <div className="doc-list__row" key={file.fileId || `${file.name}-${index}`}>
              <div className="doc__file-icon" aria-hidden="true">
                <svg width="20" height="24" viewBox="0 0 20 24"><path d="M2 1h10l6 6v16H2z" fill="none" stroke="currentColor" strokeWidth="1.2"/><path d="M12 1v6h6" fill="none" stroke="currentColor" strokeWidth="1.2"/></svg>
              </div>
              <div className="doc__file-meta">
                <div className="doc__file-name">{file.name}</div>
                <div className="doc__file-size">
                  {formatFileSize(file.size)}
                  {file.detectedDocumentType ? ` · ${file.detectedDocumentType}` : ' · awaiting classification'}
                </div>
              </div>
              <span className="doc-list__status">{file.extractionStatus || 'not_started'}</span>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

function SchemaFieldControl({ field, answers, setAnswer, uploadFile }) {
  const localKey = localKeyForSchemaField(field);
  const options = schemaChoiceOptions(field);
  if (!localKey) {
    return (
      <Field label={field.label} hint={`Schema field is not mapped: ${field.question_key}`} required={field.required} span={2}>
        <TextInput value="" onChange={() => {}} placeholder="This field needs a frontend mapping." />
      </Field>
    );
  }

  const value = answers[localKey];
  const onChange = (next) => setAnswer(localKey, next);

  if (field.field_type === 'file') {
    return (
      <DocSlot
        k={localKey}
        label={field.label}
        hint={field.help_text || ''}
        value={value}
        onChange={onChange}
        uploadFile={uploadFile}
      />
    );
  }

  let control;
  if (field.field_type === 'number') {
    control = (
      <NumberInput
        value={value}
        onChange={onChange}
        suffix={schemaNumberSuffix(field)}
        placeholder={field.placeholder || ''}
      />
    );
  } else if (field.field_type === 'textarea') {
    control = <Textarea value={value} onChange={onChange} placeholder={field.placeholder || ''} />;
  } else if (field.field_type === 'select') {
    control = schemaIsYesNo(field)
      ? <YesNo value={value} onChange={onChange} />
      : <Chips multi={false} value={value} onChange={onChange} options={options} />;
  } else if (field.field_type === 'multiselect') {
    control = options.length
      ? <Chips value={value} onChange={onChange} options={options} />
      : <TextInput value={value} onChange={onChange} placeholder={field.placeholder || 'Comma-separated values'} />;
  } else if (field.field_type === 'multiselect_freeform') {
    control = options.length
      ? <Chips value={value} onChange={onChange} options={options} />
      : <TextInput value={value} onChange={onChange} placeholder={field.placeholder || 'Comma-separated values'} />;
  } else {
    control = <TextInput type={schemaInputType(field)} value={value} onChange={onChange} placeholder={field.placeholder || ''} />;
  }

  return (
    <Field
      label={field.label}
      hint={field.help_text}
      required={field.required}
      span={schemaFieldSpan(field)}
      fieldKey={localKey}
    >
      {control}
      <FallbackAnswerControls field={field} value={value} onChange={onChange} />
    </Field>
  );
}

function FallbackAnswerControls({ field, value, onChange }) {
  const choices = [];
  if (field.allow_unknown) choices.push(['unknown', 'Unknown']);
  if (field.allow_not_applicable) choices.push(['not_applicable', 'Not applicable']);
  if (!choices.length) return null;
  return (
    <div className="fallbacks">
      {choices.map(([raw, label]) => (
        <button
          key={raw}
          type="button"
          className={`fallback ${value === raw ? 'fallback--on' : ''}`}
          onClick={() => onChange(value === raw ? '' : raw)}
        >
          {label}
        </button>
      ))}
    </div>
  );
}

function PrefillReviewPanel({
  uploadedFileIds,
  documentStatus,
  prefillReview,
  prefillStatus,
  questionIndex,
  onAnalyze,
  onAccept,
  onReject,
}) {
  const needsReview = prefillReview?.needs_review || [];
  const conflicts = prefillReview?.conflicts || [];
  const autoAccepted = prefillReview?.auto_accepted_question_keys || [];
  const files = documentStatus?.files || [];
  const hasUploads = uploadedFileIds.length > 0 || files.length > 0;
  return (
    <div className="prefill">
      <div className="prefill__top">
        <div>
          <div className="prefill__k serif">Document prefill</div>
          <p>{hasUploads ? `${files.length || uploadedFileIds.length} uploaded file${(files.length || uploadedFileIds.length) === 1 ? '' : 's'} available for extraction.` : 'Upload documents to enable answer extraction.'}</p>
        </div>
        <button className="btn btn--ghost btn--sm" onClick={onAnalyze} disabled={!hasUploads || prefillStatus?.state === 'processing'}>
          {prefillStatus?.state === 'processing' ? 'Analyzing...' : 'Analyze uploaded documents'}
        </button>
      </div>
      <div className="prefill__stats">
        <span>{autoAccepted.length} auto-filled</span>
        <span>{needsReview.length} need review</span>
        <span>{conflicts.length} conflict{conflicts.length === 1 ? '' : 's'}</span>
      </div>
      {prefillStatus?.message && <div className="prefill__status">{prefillStatus.message}</div>}
      {[...conflicts, ...needsReview].length > 0 && (
        <div className="prefill__items">
          {[...conflicts, ...needsReview].map((item) => (
            <PrefillCandidate
              key={item.id}
              item={item}
              questionIndex={questionIndex}
              onAccept={() => onAccept(item.id)}
              onReject={() => onReject(item.id)}
            />
          ))}
        </div>
      )}
    </div>
  );
}

function PrefillCandidate({ item, questionIndex, onAccept, onReject }) {
  const field = getFieldForQuestion(questionIndex, item.question_key);
  const label = field?.label || item.question_key;
  const value = item.normalized_value ?? item.candidate_value;
  return (
    <div className={`prefill-card prefill-card--${item.status}`}>
      <div className="prefill-card__hd">
        <span>{item.status === 'conflict' ? 'Conflict' : 'Review'}</span>
        {typeof item.confidence === 'number' && <b>{Math.round(item.confidence * 100)}%</b>}
      </div>
      <div className="prefill-card__field">{label}</div>
      <div className="prefill-card__value">{Array.isArray(value) ? value.join(', ') : String(value ?? '')}</div>
      {item.evidence_text && <div className="prefill-card__evidence">{item.evidence_text}</div>}
      <div className="prefill-card__actions">
        <button className="btn btn--primary btn--sm" onClick={onAccept}>Accept</button>
        <button className="btn btn--ghost btn--sm" onClick={onReject}>Reject</button>
      </div>
    </div>
  );
}

// =========================================================================
// REVIEW SUMMARY
// =========================================================================
function ReviewSectionContent({ answers, onSubmit, submitError, apiStatus, completion }) {
  return (
    <div className="review">
      <div className="review__lede">
        <p>Please review your Level 1 qualification. Once submitted, NeevCloud&apos;s infrastructure team will review AI-ready site fit before deciding whether deeper diligence is needed for future regional tenders.</p>
      </div>
      <ReviewCard title="Company" rows={[
        ['Legal name', answers.company_legal_name],
        ['Brand', answers.company_brand_name],
        ['Type', answers.company_type],
        ['Representative type', answers.representative_type],
      ]} />
      <ReviewCard title="Primary contact" rows={[
        ['Name', answers.contact_full_name],
        ['Email', answers.contact_email],
        ['Phone / WhatsApp', answers.contact_phone_or_whatsapp],
        ['Preferred', answers.preferred_contact_method],
      ]} />
      <ReviewCard title="Facility" rows={[
        ['Name', answers.facility_name],
        ['City', answers.facility_city],
        ['Country', answers.facility_country],
        ['Stage', answers.facility_stage],
      ]} />
      <ReviewCard title="Power" rows={[
        ['Total site', fmt(answers.total_site_power_mw, 'MW')],
        ['Vacant IT load', fmt(answers.vacant_it_load_mw, 'MW')],
        ['Within 6 months', fmt(answers.power_available_3_6_months_mw, 'MW')],
      ]} />
      <ReviewCard title="NVIDIA-class readiness" rows={[
        ['Earliest deployment-ready', answers.earliest_deployment_start_date],
        ['Cooling type', answers.cooling_type],
        ['Current rack density', fmt(answers.current_rack_density_kw, 'kW/rack')],
        ['Max rack density', fmt(answers.max_rack_density_after_upgrade_kw, 'kW/rack')],
      ]} />
      <ReviewCard title="Commercial" rows={[
        ['Host NeevCloud-owned / customer-backed GPUs', answers.willing_host_neevcloud_gpus],
      ]} />
      <div className="review__submit">
        <div className="review__pct">
          <div className="review__pct-num serif">{completion}%</div>
          <div className="review__pct-l">Level 1 fields completed</div>
        </div>
        <div className="review__legal">
          By registering you confirm you&apos;re authorized to share this facility for NeevCloud IDN review. Registration is not a commercial commitment, purchase order, or tender guarantee by either party.
        </div>
        {submitError && <div className="review__error">{submitError}</div>}
        <button
          className="btn btn--primary btn--xl"
          onClick={onSubmit}
          disabled={completion < 100 || apiStatus?.state === 'submitting' || apiStatus?.state === 'saving'}
        >
          {apiStatus?.state === 'submitting' ? 'Submitting...' : 'Submit Level 1 Qualification ->'}
        </button>
      </div>
    </div>
  );
}
function fmt(v, unit) {
  if (v === undefined || v === null || v === '') return '';
  return `${v} ${unit}`;
}

function HorizonInput({ label, value, onChange }) {
  return (
    <div className="horizon">
      <div className="horizon__l">{label}</div>
      <div className="input input--with-suffix">
        <input type="number" value={value ?? ''} onChange={(e) => onChange(e.target.value === '' ? '' : Number(e.target.value))} placeholder="0" />
        <span className="input__suffix">MW</span>
      </div>
    </div>
  );
}

function DocSlot({ k, label, hint, value, onChange, uploadFile }) {
  const inputRef = useRef(null);
  const [uploading, setUploading] = useState(false);
  const [error, setError] = useState('');
  const onPick = () => inputRef.current?.click();
  const onFile = async (file) => {
    if (!file) return;
    setError('');
    setUploading(true);
    try {
      const uploaded = uploadFile ? await uploadFile(k, file) : {
        name: file.name,
        size: file.size,
        uploadedAt: new Date().toISOString(),
      };
      onChange(uploaded);
    } catch (err) {
      setError(err.message || 'Upload failed');
    } finally {
      setUploading(false);
    }
  };
  return (
    <div className={`doc ${value ? 'doc--filled' : ''}`}>
      <div className="doc__hd">
        <div className="doc__l serif">{label}</div>
        <div className="doc__h">{hint}</div>
      </div>
      <div className="doc__body">
        {value ? (
          <div className="doc__file">
            <div className="doc__file-icon" aria-hidden="true">
              <svg width="20" height="24" viewBox="0 0 20 24"><path d="M2 1h10l6 6v16H2z" fill="none" stroke="currentColor" strokeWidth="1.2"/><path d="M12 1v6h6" fill="none" stroke="currentColor" strokeWidth="1.2"/></svg>
            </div>
            <div className="doc__file-meta">
              <div className="doc__file-name">{value.name}</div>
              <div className="doc__file-size">{(value.size / (1024 * 1024)).toFixed(1)} MB · uploaded</div>
            </div>
            <button className="linkbtn" onClick={() => onChange(null)}>Remove</button>
          </div>
        ) : (
          <button className="doc__drop" onClick={onPick} disabled={uploading}>
            <span className="doc__drop-icon" aria-hidden="true">
              <svg width="22" height="22" viewBox="0 0 22 22"><path d="M11 3V14M11 14L6 9M11 14L16 9M3 17V19H19V17" stroke="currentColor" strokeWidth="1.2" fill="none"/></svg>
            </span>
            <span>{uploading ? 'Uploading...' : <>Drop file or <u>browse</u></>}</span>
            <span className="doc__drop-hint">PDF · DWG · PNG · JPG · ZIP · max 50 MB</span>
          </button>
        )}
        <input
          ref={inputRef}
          type="file"
          hidden
          onChange={(e) => onFile(e.target.files?.[0])}
        />
        {error && <div className="doc__error">{error}</div>}
      </div>
    </div>
  );
}

function pickFakeFilename(k) {
  const map = {
    doc_site_documents: 'site-documents-bundle.zip',
  };
  return map[k] || 'document.pdf';
}

function formatFileSize(size) {
  const n = Number(size);
  if (!Number.isFinite(n)) return 'uploaded';
  if (n >= 1024 * 1024) return `${(n / (1024 * 1024)).toFixed(1)} MB`;
  if (n >= 1024) return `${Math.round(n / 1024)} KB`;
  return `${n} bytes`;
}

function ReviewCard({ title, rows }) {
  return (
    <div className="rcard">
      <div className="rcard__hd serif">{title}</div>
      <dl className="rcard__rows">
        {rows.map(([k, v], i) => (
          <div className="rcard__row" key={i}>
            <dt>{k}</dt>
            <dd>{v || <span className="ink-4">— not provided</span>}</dd>
          </div>
        ))}
      </dl>
    </div>
  );
}

// =========================================================================
// ADMIN PORTAL
// =========================================================================
function AdminShell({ children, title, kicker = 'Internal review queue' }) {
  const { nav } = useRoute();
  const logout = async () => {
    try {
      if (window.supabase && SUPABASE_URL && SUPABASE_ANON_KEY) {
        const client = window.supabase.createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
        await client.auth.signOut();
      }
    } catch {}
    localStorage.removeItem(ADMIN_TOKEN_KEY);
    localStorage.removeItem(ADMIN_SESSION_KEY);
    nav('/admin/login');
  };
  return (
    <div className="admin-page">
      <aside className="admin-side">
        <button className="brand admin-brand" onClick={() => nav('/')}>
          <img src={NEEVCLOUD_LOGO_SRC} alt="NeevCloud" className="brand__logo" />
          <span><span className="brand__main">NeevCloud IDN</span><span className="brand__sub">Admin</span></span>
        </button>
        <nav className="admin-nav" aria-label="Admin navigation">
          <button onClick={() => nav('/admin')}>Review queue</button>
          <button onClick={() => nav('/admin/demand')}>Demand requests</button>
          <button onClick={() => nav('/admin/criteria')}>Level 2/3 criteria</button>
        </nav>
        <button className="admin-logout" onClick={logout}>Sign out</button>
      </aside>
      <main className="admin-main">
        <div className="admin-head">
          <div>
            <div className="admin-kicker">{kicker}</div>
            <h1 className="serif">{title}</h1>
          </div>
        </div>
        {children}
      </main>
    </div>
  );
}

function useAdminResource(loader, deps = []) {
  const { nav } = useRoute();
  const [state, setState] = useState({ loading: true, error: '', data: null });
  useEffect(() => {
    let alive = true;
    setState((prev) => ({ ...prev, loading: true, error: '' }));
    loader()
      .then((data) => { if (alive) setState({ loading: false, error: '', data }); })
      .catch((err) => {
        if (!alive) return;
        if (err.status === 401 || err.status === 403) nav('/admin/login');
        setState({ loading: false, error: err.message, data: null });
      });
    return () => { alive = false; };
  }, deps);
  return state;
}

function AdminLogin() {
  const { nav } = useRoute();
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [token, setToken] = useState('');
  const [status, setStatus] = useState({ state: 'idle', message: '' });
  const supabaseConfigured = Boolean(window.supabase && SUPABASE_URL && SUPABASE_ANON_KEY);

  useEffect(() => {
    if (!supabaseConfigured) return;
    const client = window.supabase.createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
    client.auth.getSession().then(({ data }) => {
      if (data?.session?.access_token) {
        localStorage.setItem(ADMIN_SESSION_KEY, JSON.stringify(data.session));
        nav('/admin');
      }
    });
  }, [supabaseConfigured]);

  const verifyAndEnter = async (accessToken) => {
    localStorage.setItem(ADMIN_TOKEN_KEY, accessToken);
    await adminApiFetch('/api/v1/admin/me');
    nav('/admin');
  };

  const signInWithPassword = async () => {
    setStatus({ state: 'loading', message: 'Signing in...' });
    try {
      if (!supabaseConfigured) throw new Error('Supabase Auth is not configured for this frontend.');
      const client = window.supabase.createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
      const { data, error } = await client.auth.signInWithPassword({ email, password });
      if (error) throw error;
      localStorage.setItem(ADMIN_SESSION_KEY, JSON.stringify(data.session));
      await verifyAndEnter(data.session.access_token);
    } catch (err) {
      setStatus({ state: 'error', message: err.message });
    }
  };

  const sendMagicLink = async () => {
    setStatus({ state: 'loading', message: 'Sending magic link...' });
    try {
      if (!supabaseConfigured) throw new Error('Supabase Auth is not configured for this frontend.');
      const client = window.supabase.createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
      const { error } = await client.auth.signInWithOtp({
        email,
        options: { emailRedirectTo: `${window.location.origin}/admin` },
      });
      if (error) throw error;
      setStatus({ state: 'sent', message: `Magic link sent to ${email}.` });
    } catch (err) {
      setStatus({ state: 'error', message: err.message });
    }
  };

  const useToken = async () => {
    setStatus({ state: 'loading', message: 'Checking token...' });
    try {
      await verifyAndEnter(token.trim());
    } catch (err) {
      localStorage.removeItem(ADMIN_TOKEN_KEY);
      setStatus({ state: 'error', message: err.message });
    }
  };

  return (
    <div className="admin-login">
      <div className="admin-login__card">
        <img src={NEEVCLOUD_LOGO_SRC} alt="NeevCloud" className="admin-login__logo" />
        <div className="admin-kicker">Internal portal</div>
        <h1 className="serif">NeevCloud IDN review queue</h1>
        <p>Sign in to review Level 1 AI-ready data-center capacity submissions.</p>
        <label>Email</label>
        <input className="input" type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="you@neevcloud.com" />
        <label>Password</label>
        <input className="input" type="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="Optional if using magic link" />
        <div className="admin-login__actions">
          <button className="btn btn--primary" onClick={signInWithPassword}>Sign in</button>
          <button className="btn btn--ghost" onClick={sendMagicLink}>Send magic link</button>
        </div>
        <div className="admin-login__dev">
          <label>Local/dev access token</label>
          <div className="admin-token-row">
            <input className="input" value={token} onChange={(e) => setToken(e.target.value)} placeholder="Only for configured dev bypass token" />
            <button className="btn btn--ghost" onClick={useToken}>Use token</button>
          </div>
        </div>
        {status.message && <div className={`admin-status admin-status--${status.state}`}>{status.message}</div>}
      </div>
    </div>
  );
}

function AdminDashboard() {
  const [filters, setFilters] = useState({ status: '', tier: '', country: '', cooling_type: '', ai_review: '', q: '' });
  const { nav } = useRoute();
  const query = new URLSearchParams(Object.entries(filters).filter(([, v]) => v)).toString();
  const state = useAdminResource(() => adminApiFetch(`/api/v1/admin/applications${query ? `?${query}` : ''}`), [query]);
  const data = state.data || { applications: [], counts: {} };

  return (
    <AdminShell title="AI-ready capacity review queue">
      <div className="admin-metrics">
        {['total', 'level_1_submitted', 'under_review', 'needs_more_info', 'qualified_for_deep_dive', 'not_current_fit'].map((key) => (
          <div className="admin-metric" key={key}>
            <span>{key.replaceAll('_', ' ')}</span>
            <b>{data.counts?.[key] || 0}</b>
          </div>
        ))}
      </div>
      <div className="admin-filters">
        <input className="input" placeholder="Search company, facility, city..." value={filters.q} onChange={(e) => setFilters({ ...filters, q: e.target.value })} />
        <select value={filters.status} onChange={(e) => setFilters({ ...filters, status: e.target.value })}>
          <option value="">All statuses</option>
          {['level_1_submitted', 'under_review', 'needs_more_info', 'qualified_for_deep_dive', 'not_current_fit'].map((v) => <option key={v} value={v}>{v.replaceAll('_', ' ')}</option>)}
        </select>
        <select value={filters.tier} onChange={(e) => setFilters({ ...filters, tier: e.target.value })}>
          <option value="">All tiers</option>
          {['unassigned', 'tier_1', 'tier_2', 'tier_3', 'not_current_fit'].map((v) => <option key={v} value={v}>{v.replaceAll('_', ' ')}</option>)}
        </select>
        <select value={filters.ai_review} onChange={(e) => setFilters({ ...filters, ai_review: e.target.value })}>
          <option value="">AI review</option>
          <option value="not_run">Not run</option>
          <option value="complete">Complete</option>
        </select>
      </div>
      {state.loading ? <div className="admin-empty">Loading applications...</div> : state.error ? <div className="admin-error">{state.error}</div> : (
        <div className="admin-table-wrap">
          <table className="admin-table">
            <thead>
              <tr>
                <th>Status</th><th>Tier</th><th>Company / facility</th><th>Location</th><th>Power</th><th>Rack density</th><th>Cooling</th><th>Ready date</th><th>AI</th>
              </tr>
            </thead>
            <tbody>
              {data.applications.map((row) => (
                <tr key={row.id} onClick={() => nav(`/admin/applications/${row.id}`)}>
                  <td><span className="admin-pill">{row.review_status?.replaceAll('_', ' ')}</span></td>
                  <td>{row.tier?.replaceAll('_', ' ')}</td>
                  <td><b>{row.company_legal_name || '—'}</b><span>{row.facility_name || row.submission_reference}</span></td>
                  <td>{[row.facility_city, row.facility_country].filter(Boolean).join(', ') || '—'}</td>
                  <td>{fmt(row.power_vacant_it_load_mw, 'MW') || '—'} today<br />{fmt(row.power_available_3_6_months_mw, 'MW') || '—'} / 6 mo</td>
                  <td>{fmt(row.cooling_current_rack_density_kw, 'kW/rack') || '—'}<br />{fmt(row.cooling_max_rack_density_after_upgrade_kw, 'kW/rack') || '—'} max</td>
                  <td>{humanize(row.cooling_type)}</td>
                  <td>{row.earliest_deployment_ready_date || '—'}</td>
                  <td>{row.ai_review_state === 'complete' ? 'Done' : 'Pending'}</td>
                </tr>
              ))}
            </tbody>
          </table>
          {!data.applications.length && <div className="admin-empty">No applications match the current filters.</div>}
        </div>
      )}
    </AdminShell>
  );
}

function AdminDemandDashboard() {
  const [filters, setFilters] = useState({ status: '', target_region: '', gpu_class: '', workload_type: '', q: '' });
  const { nav } = useRoute();
  const query = new URLSearchParams(Object.entries(filters).filter(([, v]) => v)).toString();
  const state = useAdminResource(() => adminApiFetch(`/api/v1/admin/demand-requests${query ? `?${query}` : ''}`), [query]);
  const data = state.data || { requests: [], counts: {} };

  return (
    <AdminShell title="Demand compute requests" kicker="Internal demand review queue">
      <div className="admin-metrics">
        {['total', 'demand_submitted', 'under_review', 'needs_more_info', 'qualified_for_compute_review', 'converted_to_opportunity'].map((key) => (
          <div className="admin-metric" key={key}>
            <span>{key.replaceAll('_', ' ')}</span>
            <b>{data.counts?.[key] || 0}</b>
          </div>
        ))}
      </div>
      <div className="admin-filters">
        <input className="input" placeholder="Search company, customer type, region..." value={filters.q} onChange={(e) => setFilters({ ...filters, q: e.target.value })} />
        <select value={filters.status} onChange={(e) => setFilters({ ...filters, status: e.target.value })}>
          <option value="">All statuses</option>
          {['demand_submitted', 'under_review', 'needs_more_info', 'qualified_for_compute_review', 'not_current_fit', 'converted_to_opportunity'].map((v) => <option key={v} value={v}>{v.replaceAll('_', ' ')}</option>)}
        </select>
        <select value={filters.target_region} onChange={(e) => setFilters({ ...filters, target_region: e.target.value })}>
          <option value="">All regions</option>
          {['india', 'middle_east', 'europe', 'north_america', 'apac', 'multi_region', 'flexible_first_available'].map((v) => <option key={v} value={v}>{humanize(v)}</option>)}
        </select>
        <select value={filters.gpu_class} onChange={(e) => setFilters({ ...filters, gpu_class: e.target.value })}>
          <option value="">GPU class</option>
          {['best_available', 'h100_h200_class', 'b200_gb200_class', 'l40s_class', 'cost_optimized_gpu', 'need_neevcloud_recommendation'].map((v) => <option key={v} value={v}>{humanize(v)}</option>)}
        </select>
      </div>
      {state.loading ? <div className="admin-empty">Loading demand requests...</div> : state.error ? <div className="admin-error">{state.error}</div> : (
        <div className="admin-table-wrap">
          <table className="admin-table">
            <thead>
              <tr>
                <th>Status</th><th>Company</th><th>Customer type</th><th>Workload</th><th>GPU class</th><th>Scale</th><th>Region</th><th>Window</th><th>Submitted</th>
              </tr>
            </thead>
            <tbody>
              {data.requests.map((row) => (
                <tr key={row.id} onClick={() => nav(`/admin/demand/${row.id}`)}>
                  <td><span className="admin-pill">{humanize(row.status)}</span></td>
                  <td><b>{row.company_name || '—'}</b><span>{row.request_reference}</span></td>
                  <td>{humanize(row.customer_type)}</td>
                  <td>{humanize(row.workload_type)}</td>
                  <td>{humanize(row.gpu_class)}</td>
                  <td>{humanize(row.scale_needed)}</td>
                  <td>{humanize(row.target_region)}</td>
                  <td>{humanize(row.deployment_window)}</td>
                  <td>{formatDate(row.submitted_at || row.created_at)}</td>
                </tr>
              ))}
            </tbody>
          </table>
          {!data.requests.length && <div className="admin-empty">No demand requests match the current filters.</div>}
        </div>
      )}
    </AdminShell>
  );
}

function AdminDemandDetail({ requestId }) {
  const [refreshKey, setRefreshKey] = useState(0);
  const [note, setNote] = useState('');
  const [busy, setBusy] = useState('');
  const state = useAdminResource(() => adminApiFetch(`/api/v1/admin/demand-requests/${requestId}`), [requestId, refreshKey]);
  const detail = state.data;
  const request = detail?.request || {};
  const review = detail?.review || {};

  const updateReview = async (payload) => {
    setBusy('review');
    await adminApiFetch(`/api/v1/admin/demand-requests/${requestId}/review`, { method: 'PATCH', body: JSON.stringify(payload) });
    setBusy('');
    setRefreshKey((v) => v + 1);
  };
  const addNote = async () => {
    if (!note.trim()) return;
    setBusy('note');
    await adminApiFetch(`/api/v1/admin/demand-requests/${requestId}/notes`, { method: 'POST', body: JSON.stringify({ body: note.trim() }) });
    setNote('');
    setBusy('');
    setRefreshKey((v) => v + 1);
  };

  return (
    <AdminShell title={request.company_name || 'Demand request'} kicker={request.request_reference || 'Demand'}>
      {state.loading ? <div className="admin-empty">Loading demand request...</div> : state.error ? <div className="admin-error">{state.error}</div> : (
        <>
          <div className="admin-detail-grid">
            <div className="admin-summary">
              <h2 className="serif">{request.company_name || '—'}</h2>
              <p>{humanize(request.customer_type)} · {humanize(request.workload_type)}</p>
              <div className="admin-actions">
                <select value={review.status || 'demand_submitted'} onChange={(e) => updateReview({ status: e.target.value })}>
                  {['demand_submitted', 'under_review', 'needs_more_info', 'qualified_for_compute_review', 'not_current_fit', 'converted_to_opportunity'].map((v) => <option key={v} value={v}>{humanize(v)}</option>)}
                </select>
              </div>
            </div>
            <AdminReadinessCard title="Compute" rows={[['GPU class', humanize(request.gpu_class)], ['Scale', humanize(request.scale_needed)], ['Workload', humanize(request.workload_type)]]} />
            <AdminReadinessCard title="Region" rows={[['Target', humanize(request.target_region)], ['Window', humanize(request.deployment_window)], ['Preference', humanize(request.deployment_preference)]]} />
            <AdminReadinessCard title="Contact" rows={[['Name', request.contact_name], ['Email', request.contact_email], ['Phone', request.contact_phone_or_whatsapp]]} />
          </div>

          <div className="admin-panels">
            <section className="admin-panel admin-panel--wide">
              <div className="admin-panel__hd"><h3>Selector answers</h3></div>
              <table className="admin-table admin-table--compact">
                <tbody>
                  {detail.answers.map((row) => (
                    <tr key={row.question_key}>
                      <td>{row.label}<span>{row.question_key}</span></td>
                      <td>{adminValue(row.value)}</td>
                      <td>{row.source}</td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </section>
            <section className="admin-panel">
              <div className="admin-panel__hd"><h3>Notes and context</h3></div>
              <p className="ink-3">{request.notes || 'No notes provided.'}</p>
              {request.model_workload_description && <p>{request.model_workload_description}</p>}
              {request.compliance_data_residency && <p>{request.compliance_data_residency}</p>}
            </section>
            <section className="admin-panel">
              <div className="admin-panel__hd"><h3>Files</h3></div>
              {detail.files.map((file) => <div className="admin-list-row" key={file.id}><b>{file.original_filename}</b><span>{file.file_category} · {file.content_type || 'file'}</span></div>)}
              {!detail.files.length && <p className="ink-3">No files uploaded.</p>}
            </section>
            <section className="admin-panel">
              <div className="admin-panel__hd"><h3>Admin notes</h3></div>
              <textarea className="input admin-note-box" value={note} onChange={(e) => setNote(e.target.value)} placeholder="Add internal demand review note..." />
              <button className="btn btn--ghost" onClick={addNote} disabled={busy === 'note'}>Add note</button>
              {detail.notes.map((item) => <div className="admin-note" key={item.id}><b>{item.author_email}</b><p>{item.body}</p></div>)}
            </section>
            <section className="admin-panel">
              <div className="admin-panel__hd"><h3>Timeline</h3></div>
              {detail.timeline.map((event, i) => <div className="admin-list-row" key={`${event.type}-${i}`}><b>{event.label}</b><span>{event.created_at || '—'}</span></div>)}
            </section>
          </div>
        </>
      )}
    </AdminShell>
  );
}

function AdminApplicationDetail({ applicationId }) {
  const [refreshKey, setRefreshKey] = useState(0);
  const [note, setNote] = useState('');
  const [correction, setCorrection] = useState(null);
  const [busy, setBusy] = useState('');
  const state = useAdminResource(() => adminApiFetch(`/api/v1/admin/applications/${applicationId}`), [applicationId, refreshKey]);
  const detail = state.data;
  const app = detail?.application || {};
  const review = detail?.review || {};
  const latestAI = detail?.ai_reviews?.[0];

  const updateReview = async (payload) => {
    setBusy('review');
    await adminApiFetch(`/api/v1/admin/applications/${applicationId}/review`, { method: 'PATCH', body: JSON.stringify(payload) });
    setBusy('');
    setRefreshKey((v) => v + 1);
  };
  const addNote = async () => {
    if (!note.trim()) return;
    setBusy('note');
    await adminApiFetch(`/api/v1/admin/applications/${applicationId}/notes`, { method: 'POST', body: JSON.stringify({ body: note.trim() }) });
    setNote('');
    setBusy('');
    setRefreshKey((v) => v + 1);
  };
  const runAIReview = async () => {
    setBusy('ai');
    await adminApiFetch(`/api/v1/admin/applications/${applicationId}/ai-review`, { method: 'POST', body: JSON.stringify({}) });
    setBusy('');
    setRefreshKey((v) => v + 1);
  };

  return (
    <AdminShell title={app.facility_name || 'Application detail'} kicker={app.submission_reference || 'Application'}>
      {state.loading ? <div className="admin-empty">Loading application...</div> : state.error ? <div className="admin-error">{state.error}</div> : (
        <>
          <div className="admin-detail-grid">
            <div className="admin-summary">
              <h2 className="serif">{app.company_legal_name || '—'}</h2>
              <p>{[app.facility_city, app.facility_country].filter(Boolean).join(', ') || 'Location not provided'}</p>
              <div className="admin-actions">
                <select value={review.status || 'level_1_submitted'} onChange={(e) => updateReview({ status: e.target.value })}>
                  {['level_1_submitted', 'under_review', 'needs_more_info', 'qualified_for_deep_dive', 'not_current_fit'].map((v) => <option key={v} value={v}>{v.replaceAll('_', ' ')}</option>)}
                </select>
                <select value={review.tier || 'unassigned'} onChange={(e) => updateReview({ tier: e.target.value })}>
                  {['unassigned', 'tier_1', 'tier_2', 'tier_3', 'not_current_fit'].map((v) => <option key={v} value={v}>{v.replaceAll('_', ' ')}</option>)}
                </select>
                <button className="btn btn--primary" onClick={runAIReview} disabled={busy === 'ai'}>{busy === 'ai' ? 'Running...' : 'Run AI Assist'}</button>
              </div>
            </div>
            <AdminReadinessCard title="Power" rows={[['Vacant today', fmt(app.power_vacant_it_load_mw, 'MW')], ['Within 6 months', fmt(app.power_available_3_6_months_mw, 'MW')], ['Total site', fmt(app.power_total_site_power_mw, 'MW')]]} />
            <AdminReadinessCard title="Cooling" rows={[['Current density', fmt(app.cooling_current_rack_density_kw, 'kW/rack')], ['Max density', fmt(app.cooling_max_rack_density_after_upgrade_kw, 'kW/rack')], ['Cooling type', humanize(app.cooling_type)]]} />
            <AdminReadinessCard title="Commercial" rows={[['Host GPUs', humanize(app.commercial_willing_host_neevcloud_gpus)], ['Stage', humanize(app.facility_stage)], ['Status', humanize(review.status)]]} />
          </div>

          <div className="admin-panels">
            <section className="admin-panel admin-panel--wide">
              <div className="admin-panel__hd"><h3>Level 1 answers</h3></div>
              <table className="admin-table admin-table--compact">
                <tbody>
                  {detail.answers.map((row) => (
                    <tr key={row.question_key}>
                      <td>{row.label}<span>{row.question_key}</span></td>
                      <td>{adminValue(row.value)}</td>
                      <td>{row.corrected ? <b>Corrected: {adminValue(row.corrected_value)}</b> : row.source}</td>
                      <td><button className="mini-btn" onClick={() => setCorrection(row)}>Correct</button></td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </section>
            <section className="admin-panel">
              <div className="admin-panel__hd"><h3>AI Assist</h3></div>
              {latestAI ? <AdminAIMemo review={latestAI} /> : <p className="ink-3">No AI review memo yet.</p>}
            </section>
            <section className="admin-panel">
              <div className="admin-panel__hd"><h3>Documents</h3></div>
              {detail.files.map((file) => <div className="admin-list-row" key={file.id}><b>{file.original_filename}</b><span>{file.detected_document_type || file.file_category} · {file.extraction_status}</span></div>)}
              {!detail.files.length && <p className="ink-3">No documents uploaded.</p>}
            </section>
            <section className="admin-panel">
              <div className="admin-panel__hd"><h3>Diligence signals</h3></div>
              {detail.level_2_3_signals.slice(0, 12).map((signal) => <div className="admin-list-row" key={signal.question_key}><b>{signal.label}</b><span>{signal.level} · {adminValue(signal.value)}</span></div>)}
              {!detail.level_2_3_signals.length && <p className="ink-3">No Level 2/3 signals yet.</p>}
            </section>
            <section className="admin-panel">
              <div className="admin-panel__hd"><h3>Notes</h3></div>
              <textarea className="input admin-note-box" value={note} onChange={(e) => setNote(e.target.value)} placeholder="Add internal review note..." />
              <button className="btn btn--ghost" onClick={addNote} disabled={busy === 'note'}>Add note</button>
              {detail.notes.map((item) => <div className="admin-note" key={item.id}><b>{item.author_email}</b><p>{item.body}</p></div>)}
            </section>
            <section className="admin-panel">
              <div className="admin-panel__hd"><h3>Audit timeline</h3></div>
              {detail.timeline.map((event, i) => <div className="admin-list-row" key={`${event.type}-${i}`}><b>{event.label}</b><span>{event.created_at || '—'}</span></div>)}
            </section>
          </div>
          {correction && <AdminCorrectionModal applicationId={applicationId} row={correction} onClose={() => setCorrection(null)} onSaved={() => { setCorrection(null); setRefreshKey((v) => v + 1); }} />}
        </>
      )}
    </AdminShell>
  );
}

function AdminReadinessCard({ title, rows }) {
  return <div className="admin-readiness"><span>{title}</span>{rows.map(([k, v]) => <div key={k}><b>{v || '—'}</b><small>{k}</small></div>)}</div>;
}

function AdminAIMemo({ review }) {
  const memo = review.memo || {};
  return (
    <div className="admin-ai">
      <p>{memo.executive_summary}</p>
      <div className="admin-ai__tags"><span>{humanize(memo.suggested_tier)}</span><span>{humanize(memo.suggested_status)}</span><span>{Math.round((memo.confidence || 0) * 100)}% confidence</span></div>
      <h4>Risks</h4>
      <ul>{(memo.risks || []).map((item, i) => <li key={i}>{item}</li>)}</ul>
      <h4>Follow-up</h4>
      <ul>{(memo.follow_up_questions || []).map((item, i) => <li key={i}>{item}</li>)}</ul>
    </div>
  );
}

function AdminCorrectionModal({ applicationId, row, onClose, onSaved }) {
  const [value, setValue] = useState(adminValue(row.value));
  const [reason, setReason] = useState('');
  const [error, setError] = useState('');
  const save = async () => {
    setError('');
    if (!reason.trim()) {
      setError('A correction reason is required.');
      return;
    }
    try {
      await adminApiFetch(`/api/v1/admin/applications/${applicationId}/corrections`, {
        method: 'POST',
        body: JSON.stringify({ question_key: row.question_key, corrected_value: value, reason }),
      });
      onSaved();
    } catch (err) {
      setError(err.message);
    }
  };
  return (
    <div className="modal-bg" onClick={onClose}>
      <div className="modal" onClick={(e) => e.stopPropagation()}>
        <div className="modal__hd serif">Correct answer</div>
        <p>{row.label}</p>
        <label>Reviewed value</label>
        <input className="input" value={value} onChange={(e) => setValue(e.target.value)} />
        <label>Reason</label>
        <textarea className="input admin-note-box" value={reason} onChange={(e) => setReason(e.target.value)} placeholder="Why is this correction needed?" />
        {error && <div className="admin-error">{error}</div>}
        <div className="modal__row">
          <button className="btn btn--ghost" onClick={onClose}>Cancel</button>
          <button className="btn btn--primary" onClick={save}>Save correction</button>
        </div>
      </div>
    </div>
  );
}

function AdminCriteria() {
  const state = useAdminResource(() => adminApiFetch('/api/v1/admin/site-selection-criteria'), []);
  const criteria = state.data?.criteria || [];
  return (
    <AdminShell title="Level 2/3 site-selection criteria">
      {state.loading ? <div className="admin-empty">Loading criteria...</div> : state.error ? <div className="admin-error">{state.error}</div> : (
        <div className="admin-table-wrap">
          <table className="admin-table">
            <thead><tr><th>Level</th><th>Category</th><th>Question</th><th>Type</th><th>Unit</th></tr></thead>
            <tbody>{criteria.map((row) => <tr key={row.question_key}><td>{row.level}</td><td>{row.category}</td><td><b>{row.label}</b><span>{row.question_key}</span></td><td>{row.field_type}</td><td>{row.unit || '—'}</td></tr>)}</tbody>
          </table>
        </div>
      )}
    </AdminShell>
  );
}

function humanize(value) {
  if (value === null || value === undefined || value === '') return '—';
  return String(value).replaceAll('_', ' ');
}

function formatDate(value) {
  if (!value) return '—';
  const date = new Date(value);
  if (Number.isNaN(date.getTime())) return String(value);
  return date.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' });
}

function adminValue(value) {
  if (value === null || value === undefined || value === '') return '—';
  if (Array.isArray(value)) return value.join(', ');
  if (typeof value === 'object') return JSON.stringify(value);
  return String(value);
}

// =========================================================================
// MODALS
// =========================================================================
function ResumeModal({ onClose }) {
  const [email, setEmail] = useState('');
  const [sent, setSent] = useState(false);
  return (
    <div className="modal-bg" onClick={onClose}>
      <div className="modal" onClick={(e) => e.stopPropagation()}>
        <div className="modal__hd serif">Save & continue later</div>
        <p>We&apos;ll email you a link to resume this submission. Your draft is already saved on this device.</p>
        {!sent ? (
          <>
            <input className="input" type="email" placeholder="you@company.com" value={email} onChange={(e) => setEmail(e.target.value)} />
            <div className="modal__row">
              <button className="btn btn--ghost" onClick={onClose}>Cancel</button>
              <button className="btn btn--primary" onClick={() => setSent(true)}>Email me a resume link</button>
            </div>
          </>
        ) : (
          <>
            <div className="modal__success">
              <span className="check" aria-hidden="true">✓</span>
              Sent. Check your inbox at <b>{email}</b>.
            </div>
            <div className="modal__row">
              <button className="btn btn--primary" onClick={onClose}>Back to form</button>
            </div>
          </>
        )}
      </div>
    </div>
  );
}

// VoiceModal removed — voice intake is now an in-app side panel (see VoicePanel).

// =========================================================================
// THANK YOU
// =========================================================================
function ThankYou() {
  const { nav } = useRoute();
  const { answers, reset, submission } = useForm();
  const fallbackRef = useMemo(() => 'NEEV-' + Math.random().toString(36).slice(2, 8).toUpperCase() + '-' + Math.random().toString(36).slice(2, 6).toUpperCase(), []);
  const ref = submission?.submission_reference || fallbackRef;
  return (
    <div className="page">
      <TopNav onSubmit={() => nav('/submit-capacity')} />
      <section className="thanks">
        <div className="section__rule"><span>§ Level 1 submitted</span><span>Reference {ref}</span></div>
        <h1 className="serif thanks__h">
          Your site has been registered<br />
          for NeevCloud IDN Level 1 review.
        </h1>
        <p className="thanks__lede">
          The NeevCloud team will review the submission and contact you if the site fits an upcoming regional AI compute tender.
        </p>
        <div className="thanks__card">
          <div className="thanks__card-hd">
            <span>Partner registration reference</span>
            <span className="thanks__ref">{ref}</span>
          </div>
          <div className="thanks__rows">
            <div><span>Company</span><b>{answers.company_legal_name || '—'}</b></div>
            <div><span>Facility</span><b>{answers.facility_name || '—'} · {answers.facility_country || '—'}</b></div>
            <div><span>Power on tap</span><b>{fmt(answers.total_site_power_mw, 'MW') || '—'} total · {fmt(answers.vacant_it_load_mw, 'MW') || '—'} vacant</b></div>
            <div><span>NeevCloud team</span><b>Infrastructure partner registry</b></div>
            <div><span>IDN status</span><b>Level 1 submitted · awaiting review</b></div>
          </div>
        </div>
        <div className="thanks__steps">
          <h3 className="serif">What happens next</h3>
          <ol>
            <li><b>Level 1 confirmation</b> with your reference number, sent to {answers.contact_email || 'your email'}.</li>
            <li><b>Fit review</b> across site location, available power, cooling, rack density, timing, and commercial availability.</li>
            <li><b>Clarification follow-up</b> if NeevCloud needs more evidence before a deeper technical review.</li>
            <li><b>Deep-dive invitation</b> if the site fits a current or upcoming regional AI compute opportunity.</li>
            <li><b>Tender contact</b> when a relevant region, GPU lane, and deployment timeline exists.</li>
          </ol>
        </div>
        <div className="thanks__cta">
          <button className="btn btn--ghost" onClick={() => nav('/')}>← Back to NeevCloud</button>
          <button className="btn btn--primary" onClick={() => { reset(); nav('/submit-capacity'); }}>Qualify another DC site</button>
        </div>
        <p className="thanks__legal">
          Level 1 registration places your facility in the NeevCloud IDN review queue. It does not constitute a commercial commitment by NeevCloud or by you, and it does not guarantee a tender, reservation, purchase, or deployment timeline.
        </p>
      </section>
      <Footer />
    </div>
  );
}

// =========================================================================
// MOUNT
// =========================================================================
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
