'use client';

import { useEffect, useState } from 'react';
import { captureCaught } from '@/lib/observability';

export type DealStockState = {
  status: 'loading' | 'resolved' | 'error';
  stockRemaining: number;
  soldOut: boolean;
};

type StockResult = { stockRemaining: number; soldOut: boolean };
type SubscriberResult = StockResult | 'error';

const INITIAL_STATE: DealStockState = {
  status: 'loading',
  stockRemaining: 0,
  soldOut: false,
};

const DISABLED_STATE: DealStockState = {
  status: 'resolved',
  stockRemaining: 0,
  soldOut: false,
};

// Module-level micro-batcher — shared across all hook instances.
const resolved = new Map<string, StockResult>();
const inflight = new Map<string, Array<(result: SubscriberResult) => void>>();
const pending = new Set<string>();
let debounceTimer: ReturnType<typeof setTimeout> | null = null;

function notifySubscribers(id: string, result: SubscriberResult) {
  if (result !== 'error') {
    resolved.set(id, result);
  }
  const subscribers = inflight.get(id);
  if (!subscribers) return;
  for (const subscriber of subscribers) subscriber(result);
  inflight.delete(id);
}

async function fetchStock(ids: string[], retry: boolean): Promise<void> {
  try {
    const res = await fetch(`/api/deals/stock?ids=${encodeURIComponent(ids.join(','))}`, {
      headers: { Accept: 'application/json' },
    });
    if (!res.ok) throw new Error(`stock ${res.status}`);
    const { deals } = (await res.json()) as {
      deals: Record<string, StockResult>;
    };
    for (const id of ids) {
      const info = deals[id];
      notifySubscribers(id, info ?? { stockRemaining: 0, soldOut: true });
    }
  } catch (err) {
    captureCaught(err, { scope: 'features.deals.useDealStock.fetchStock', severity: 'warning' });
    if (retry) {
      await fetchStock(ids, false);
      return;
    }
    for (const id of ids) notifySubscribers(id, 'error');
  }
}

function flushPending() {
  debounceTimer = null;
  const ids = [...pending];
  pending.clear();
  if (ids.length === 0) return;
  void fetchStock(ids, true);
}

function scheduleFetch(id: string) {
  pending.add(id);
  if (debounceTimer === null) {
    debounceTimer = setTimeout(flushPending, 40);
  }
}

function subscribe(id: string, onResult: (result: SubscriberResult) => void): () => void {
  const cached = resolved.get(id);
  if (cached) {
    onResult(cached);
    return () => {};
  }

  let subscribers = inflight.get(id);
  if (!subscribers) {
    subscribers = [];
    inflight.set(id, subscribers);
    scheduleFetch(id);
  }
  subscribers.push(onResult);

  return () => {
    const current = inflight.get(id);
    if (!current) return;
    const index = current.indexOf(onResult);
    if (index >= 0) current.splice(index, 1);
  };
}

function toState(result: SubscriberResult): DealStockState {
  if (result === 'error') {
    return { status: 'error', stockRemaining: 0, soldOut: false };
  }
  return {
    status: 'resolved',
    stockRemaining: result.stockRemaining,
    soldOut: result.soldOut,
  };
}

/**
 * Client-fetched live stock for a single deal. Intentionally does NOT seed from
 * SSR props: baking stock_remaining into first paint would flash stale counts and
 * can briefly show a sold-out card before the fetch corrects it — violating
 * hide-sold-out-immediately and the cache-invalidation-free purchase path.
 */
export function useDealStock(dealId: string, options: { enabled?: boolean } = {}): DealStockState {
  const enabled = options.enabled !== false;
  const [state, setState] = useState<DealStockState>(INITIAL_STATE);

  useEffect(() => {
    if (!enabled) return;
    return subscribe(dealId, (result) => {
      setState(toState(result));
    });
  }, [dealId, enabled]);

  return enabled ? state : DISABLED_STATE;
}
