;(function () {
/* TradeStoreProvider — owns balances, positions, and live symbol prices
   for the entire SuperApp. Components read via hooks; mutators dispatch
   actions through useReducer. Initial state seeds from window.BALANCES
   (portfolio.js) and window.TRADE_SYMBOLS (trade-data.js).

   Why a single store: it's the only way to keep the home portfolio card
   in sync with closed-position P/L AND the trade subapp's per-position
   P/L AND the discover/trade pages' tick-driven prices.

   Why the trade balance is the only thing that affects total: per spec —
   "in our case only the trading numbers would affect our totals." Other
   account balances (gold, wealth, shares, savings) are static reads. */

const TradeStoreContext = React.createContext(null);

/* Initial state, seeded from existing data modules. POSITIONS starts EMPTY
   per spec — the user adds positions through the order ticket. */
function initialState() {
  /* Clone window.BALANCES so reducer mutations don't leak to other
     consumers of the bare global. */
  const balances = { ...(window.BALANCES || {}) };
  /* Symbols dict keyed by sym for O(1) lookup. */
  const symbols = {};
  for (const s of (window.TRADE_SYMBOLS || [])) {
    symbols[s.sym] = { ...s, spark: s.spark.slice() };
  }
  return {
    balances,
    positions: [],         // empty per spec
    closedPositions: [],   // historical, for Activity feed
    pendingOrders: [],     // from window.PENDING_ORDERS later if needed
    /* Pending withdrawals — created when the user submits a withdraw
       in the cash-flow sheet. They never auto-resolve in the demo;
       balances stay untouched until the withdrawal is cancelled or
       (eventually) confirmed. The wallet's pending TxRow reads from
       this list. */
    pendingWithdrawals: [],
    symbols,
    /* Monotonic version bumped by every reducer call — components that
       read globals (HomeScreen) subscribe to this so they re-render. */
    version: 0,
  };
}

/* Contract size — number of underlying units in 1 lot. Drives notional,
   margin, and P/L across the app. Single source of truth so the order
   ticket, the activity-page volume card, and computeLivePL all agree. */
function getContractSize(symOrId, cat) {
  /* Accept either a symbol object {sym, cat} or a (symId, cat) pair. */
  let symId, category;
  if (symOrId && typeof symOrId === 'object') {
    symId = symOrId.sym;
    category = symOrId.cat;
  } else {
    symId = symOrId;
    category = cat;
  }
  if (category === 'forex') return 100000;
  if (category === 'commodities' && symId === 'XAGUSD') return 5000;
  if (category === 'commodities') return 100;
  if (category === 'indices') return 1;
  /* stocks / crypto — 1 share / 1 coin per lot */
  return 1;
}

/* House leverage — single number used for margin requirements
   throughout the app. */
const LEVERAGE = 500;

/* Margin required to open / hold a position of `size` lots at `price`. */
function computeRequiredMargin(symOrId, cat, size, price) {
  const mult = getContractSize(symOrId, cat);
  return (size * mult * price) / LEVERAGE;
}

/* P/L formula — long: (current - open) * size * mult; short: flipped sign.
   `mult` is the contract multiplier inferred from the symbol category.
   Defined at module scope so reducer can use it for realised P/L. */
function computeLivePL(pos, sym) {
  if (!sym) return pos.pl || 0;
  const px = sym.bid;
  const dir = pos.side === 'buy' ? 1 : -1;
  const mult = getContractSize(pos.sym, sym.cat);
  return dir * (px - pos.open) * pos.size * mult;
}

function reducer(state, action) {
  switch (action.type) {
    case 'addPosition': {
      const next = { ...state, positions: [...state.positions, action.position], version: state.version + 1 };
      /* Margin lock — for now we simply mark it; balance doesn't change
         until the position is closed. (Live trading would lock margin
         here; we keep it simple.) */
      return next;
    }
    case 'editPosition': {
      const positions = state.positions.map((p) =>
        p.id === action.id
          ? { ...p, sl: action.sl, tp: action.tp }
          : p
      );
      return { ...state, positions, version: state.version + 1 };
    }
    case 'closePosition': {
      const pos = state.positions.find((p) => p.id === action.id);
      if (!pos) return state;
      /* Realize P/L into trading balance. */
      const realized = computeLivePL(pos, state.symbols[pos.sym]);
      const balances = { ...state.balances, trading: state.balances.trading + realized };
      const closedAt = new Date().toISOString();
      const closedPos = { ...pos, closedAt, realizedPL: realized };
      return {
        ...state,
        balances,
        positions: state.positions.filter((p) => p.id !== action.id),
        closedPositions: [closedPos, ...state.closedPositions],
        version: state.version + 1,
      };
    }
    case 'updateSymbol': {
      const cur = state.symbols[action.sym];
      if (!cur) return state;
      const next = { ...cur, ...action.partial };
      /* Two ways to update spark:
         - sparkReplace: full replacement (kept for legacy callers).
         - bid alone: append (used by the dummy feed).
         Strip sparkReplace from the merged shape so it doesn't stick. */
      if (action.partial.sparkReplace) {
        next.spark = action.partial.sparkReplace.slice(-32);
        delete next.sparkReplace;
      } else if (action.partial.bid != null) {
        next.spark = [...cur.spark, action.partial.bid].slice(-32);
      }
      return {
        ...state,
        symbols: { ...state.symbols, [action.sym]: next },
        version: state.version + 1,
      };
    }
    case 'deposit': {
      const balances = { ...state.balances, [action.account]: (state.balances[action.account] || 0) + action.amount };
      return { ...state, balances, version: state.version + 1 };
    }
    case 'withdraw': {
      const cur = state.balances[action.account] || 0;
      /* Clamp at zero — demo data, no overdrafts. */
      const next = Math.max(0, cur - action.amount);
      const balances = { ...state.balances, [action.account]: next };
      return { ...state, balances, version: state.version + 1 };
    }
    case 'transfer': {
      const fromCur = state.balances[action.from] || 0;
      const moved = Math.min(fromCur, action.amount);
      const balances = {
        ...state.balances,
        [action.from]: fromCur - moved,
        [action.to]: (state.balances[action.to] || 0) + moved,
      };
      return { ...state, balances, version: state.version + 1 };
    }
    case 'addPendingWithdrawal': {
      const entry = {
        id: 'w' + Date.now(),
        account: action.account,
        amount: action.amount,
        requestedAt: new Date().toISOString(),
      };
      return { ...state, pendingWithdrawals: [...state.pendingWithdrawals, entry], version: state.version + 1 };
    }
    default:
      return state;
  }
}

const TradeStoreProvider = ({ children }) => {
  const [state, dispatch] = React.useReducer(reducer, undefined, initialState);

  /* Mirror balance changes back to window.BALANCES so legacy code paths
     (Wallet hero SVG overlay, getTotal()) keep returning fresh values
     even if they don't subscribe to the store. */
  React.useEffect(() => {
    if (window.BALANCES) {
      Object.assign(window.BALANCES, state.balances);
    }
  }, [state.balances]);

  /* window.equiti.* bridges — non-hook accessors used by the voice
     agent's tool dispatchers. Dispatchers run from outside React's
     render cycle, so they can't call hooks; instead they read state
     via these closures and mutate via the dispatch callbacks. */
  React.useEffect(() => {
    window.equiti = window.equiti || {};
    /* Read-only bridges for the voice agent's get_* tools. */
    window.equiti.listAllSymbols = () => {
      const s = stateRef.current;
      return Object.values(s.symbols);
    };
    window.equiti.listClosedPositions = () => {
      const s = stateRef.current;
      return (s.closedPositions || []).slice();
    };
    window.equiti.listPendingWithdrawals = () => {
      const s = stateRef.current;
      return (s.pendingWithdrawals || []).slice();
    };
    window.equiti.getTradingNumbers = () => {
      const s = stateRef.current;
      const openPL = s.positions.reduce((acc, p) => acc + computeLivePL(p, s.symbols[p.sym]), 0);
      const tradingBalance = s.balances.trading;
      const equity = tradingBalance + openPL;
      const otherAccountsTotal = Object.keys(s.balances)
        .filter((k) => k !== 'trading')
        .reduce((sum, k) => sum + s.balances[k], 0);
      const total = otherAccountsTotal + equity;
      const closedPL = (s.closedPositions || []).reduce((acc, p) => acc + (p.realizedPL || 0), 0);
      return { tradingBalance, openPL, equity, total, closedPL, positionsCount: s.positions.length };
    };
    window.equiti.getFreeMargin = () => {
      const s = stateRef.current;
      const openPL = s.positions.reduce((acc, p) => acc + computeLivePL(p, s.symbols[p.sym]), 0);
      const equity = s.balances.trading + openPL;
      const usedMargin = s.positions.reduce((acc, p) => {
        const sym = s.symbols[p.sym];
        return acc + computeRequiredMargin(p.sym, sym ? sym.cat : null, p.size, p.open);
      }, 0);
      return { equity, usedMargin, freeMargin: equity - usedMargin };
    };
    window.equiti.listPositions = () => {
      const s = stateRef.current;
      return s.positions.map((p) => ({
        ...p,
        livePL: computeLivePL(p, s.symbols[p.sym]),
        currentPrice: s.symbols[p.sym] ? (p.side === 'buy' ? s.symbols[p.sym].bid : s.symbols[p.sym].ask) : null,
      }));
    };
    window.equiti.getSymbol = (sym) => {
      const s = stateRef.current.symbols[sym];
      if (!s) return null;
      return { sym: s.sym, name: s.name, bid: s.bid, ask: s.ask, cat: s.cat, realFeed: s.realFeed };
    };
    window.equiti.openTrade = (args) => {
      const { symbol, side, volume, stop_loss, take_profit } = args || {};
      const s = stateRef.current;
      const sym = s.symbols[symbol];
      if (!sym) return { ok: false, error: 'unknown symbol: ' + symbol };
      if (sym.realFeed !== 'dummy') return { ok: false, error: 'market closed for ' + symbol };
      const size = Number(volume);
      if (!isFinite(size) || size <= 0) return { ok: false, error: 'invalid volume' };
      const openPrice = side === 'buy' ? sym.ask : sym.bid;
      const requiredMargin = computeRequiredMargin(symbol, sym.cat, size, openPrice);
      const openPL = s.positions.reduce((acc, p) => acc + computeLivePL(p, s.symbols[p.sym]), 0);
      const equity = s.balances.trading + openPL;
      const usedMargin = s.positions.reduce((acc, p) => acc + computeRequiredMargin(p.sym, s.symbols[p.sym] ? s.symbols[p.sym].cat : null, p.size, p.open), 0);
      const free = equity - usedMargin;
      if (requiredMargin > free) {
        return { ok: false, error: 'insufficient free margin', requiredMargin, freeMargin: free };
      }
      const id = 'p' + Date.now();
      dispatch({
        type: 'addPosition',
        position: {
          id, sym: symbol, side, size, open: openPrice,
          sl: isFinite(stop_loss) && stop_loss > 0 ? Number(stop_loss) : undefined,
          tp: isFinite(take_profit) && take_profit > 0 ? Number(take_profit) : undefined,
          openedAt: new Date().toLocaleString('en-US', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }),
        },
      });
      return { ok: true, positionId: id, symbol, side, volume: size, openPrice, requiredMargin };
    };
    window.equiti.closePosition = (args) => {
      const { positionId, symbol } = args || {};
      const s = stateRef.current;
      let closed = [];
      if (positionId) {
        const p = s.positions.find((x) => x.id === positionId);
        if (!p) return { ok: false, error: 'position not found' };
        dispatch({ type: 'closePosition', id: positionId });
        closed.push({ id: positionId, sym: p.sym });
      } else if (symbol) {
        const ps = s.positions.filter((x) => x.sym === symbol);
        if (ps.length === 0) return { ok: false, error: 'no open position on ' + symbol };
        for (const p of ps) {
          dispatch({ type: 'closePosition', id: p.id });
          closed.push({ id: p.id, sym: p.sym });
        }
      } else {
        return { ok: false, error: 'need positionId or symbol' };
      }
      return { ok: true, closed };
    };
  }, []);

  /* The dummy feed owns every "live" symbol. The old random-walk
     tick-engine and the frankfurter forex-feed are intentionally NOT
     mounted — every symbol without `realFeed: 'dummy'` stays at its
     static seed for the lifetime of the session. No outside market
     data hits this app. */
  const stateRef = React.useRef(state);
  React.useEffect(() => { stateRef.current = state; }, [state]);
  React.useEffect(() => {
    if (!window.startDummyFeed) return;
    return window.startDummyFeed(() => stateRef.current, dispatch);
  }, []);

  const value = React.useMemo(() => ({ state, dispatch }), [state]);
  return <TradeStoreContext.Provider value={value}>{children}</TradeStoreContext.Provider>;
};

/* Hooks — read-only views over the store. */
function useTradeStore() {
  const ctx = React.useContext(TradeStoreContext);
  if (!ctx) throw new Error('useTradeStore must be used inside TradeStoreProvider');
  return ctx;
}

function useBalances() { return useTradeStore().state.balances; }
function usePendingWithdrawals() { return useTradeStore().state.pendingWithdrawals; }
function usePositions() {
  const { state } = useTradeStore();
  /* Decorate each position with live P/L so consumers don't recompute. */
  return React.useMemo(() => state.positions.map((p) => ({
    ...p,
    current: state.symbols[p.sym] ? state.symbols[p.sym].bid : p.open,
    pl: computeLivePL(p, state.symbols[p.sym]),
  })), [state.positions, state.symbols]);
}
function useClosedPositions() { return useTradeStore().state.closedPositions; }
function useSymbol(sym) { return useTradeStore().state.symbols[sym]; }
function useAllSymbols() {
  const { state } = useTradeStore();
  return React.useMemo(() => Object.values(state.symbols), [state.symbols]);
}

/* Trading numbers — used by the home portfolio card and wallet
   accounts list so they tick along with the trade subapp. */
function useTradingNumbers() {
  const { state } = useTradeStore();
  return React.useMemo(() => {
    const positions = state.positions;
    const symbols = state.symbols;
    const openPL = positions.reduce((s, p) => s + computeLivePL(p, symbols[p.sym]), 0);
    const tradingBalance = state.balances.trading;
    const equity = tradingBalance + openPL;
    /* Total portfolio value uses TRADING EQUITY, not just balance — so
       open P/L on leveraged positions ticks the home portfolio number
       live, and closing a position is a smooth realization (the number
       was already showing). Other accounts (gold/wealth/shares/savings)
       contribute their static balances. */
    const otherAccountsTotal = Object.keys(state.balances)
      .filter((k) => k !== 'trading')
      .reduce((sum, k) => sum + state.balances[k], 0);
    const total = otherAccountsTotal + equity;
    return { tradingBalance, openPL, equity, total, positions };
  }, [state]);
}

/* Free margin — equity minus margin already locked by open positions.
   The order ticket gates new trades against this so a user can't open
   a position the account can't actually support. */
function useFreeMargin() {
  const { state } = useTradeStore();
  return React.useMemo(() => {
    const positions = state.positions;
    const symbols = state.symbols;
    const openPL = positions.reduce((s, p) => s + computeLivePL(p, symbols[p.sym]), 0);
    const equity = state.balances.trading + openPL;
    const usedMargin = positions.reduce((s, p) => {
      const sym = symbols[p.sym];
      const cat = sym ? sym.cat : null;
      return s + computeRequiredMargin(p.sym, cat, p.size, p.open);
    }, 0);
    const freeMargin = equity - usedMargin;
    return { equity, usedMargin, freeMargin };
  }, [state]);
}

/* Mutators bundled — components destructure what they need. */
function useTradeMutators() {
  const { dispatch } = useTradeStore();
  return React.useMemo(() => ({
    addPosition: (position) => dispatch({ type: 'addPosition', position }),
    closePosition: (id) => dispatch({ type: 'closePosition', id }),
    /* sl / tp may be undefined to clear them. */
    editPosition: (id, sl, tp) => dispatch({ type: 'editPosition', id, sl, tp }),
    updateSymbol: (sym, partial) => dispatch({ type: 'updateSymbol', sym, partial }),
    deposit: (account, amount) => dispatch({ type: 'deposit', account, amount }),
    withdraw: (account, amount) => dispatch({ type: 'withdraw', account, amount }),
    transfer: (from, to, amount) => dispatch({ type: 'transfer', from, to, amount }),
    addPendingWithdrawal: (account, amount) => dispatch({ type: 'addPendingWithdrawal', account, amount }),
  }), [dispatch]);
}

Object.assign(window, {
  TradeStoreProvider,
  useTradeStore,
  useBalances,
  usePendingWithdrawals,
  usePositions,
  useClosedPositions,
  useSymbol,
  useAllSymbols,
  useTradingNumbers,
  useFreeMargin,
  useTradeMutators,
  computeLivePL,
  getContractSize,
  computeRequiredMargin,
  TRADE_LEVERAGE: LEVERAGE,
});
})();
