import React, { useCallback, useEffect, useState } from 'react';
import { createRoot } from 'react-dom/client';
import '@fontsource/oxanium/600.css';
import '@fontsource/oxanium/700.css';
import './styles.css';

import { lp, feeRates } from './lib/api.js';
import { useRoute } from './lib/route.js';
import { useWallet } from './lib/wallet.js';
import { LangProvider, useI18n } from './lib/i18n.jsx';
import { wait } from './lib/format.js';
import { pickGameApi, resetMock, stacks as toStacks } from './game/client.js';

import { SealDefs } from './components/Seal.jsx';
import { Backdrop } from './components/Art.jsx';
import { ToastProvider, useToast } from './components/ui.jsx';
import { Dock, PendingSheet, Rail, TopBar, WalletPicker, WalletSheet } from './components/Chrome.jsx';
import { LogoMark } from './components/Logo.jsx';
import { usePending } from './game/pending.js';
import { def } from './game/data.js';
import { nameOf } from './lib/i18n.jsx';
import { ActionModal } from './components/ActionModal.jsx';

import { Home } from './pages/Home.jsx';
import { Mint } from './pages/Mint.jsx';
import { Inventory } from './pages/Inventory.jsx';
import { Forge } from './pages/Forge.jsx';
import { Recipes } from './pages/Recipes.jsx';
import { Leaderboard } from './pages/Leaderboard.jsx';
import { Market } from './pages/Market.jsx';
import { About } from './pages/About.jsx';

// Network + backend settings. The STONK20 backend's /api/lp/config still supplies the network
// (and the UniSat chain) until /api/game/config takes over. If neither answers, the app keeps
// working in preview mode on VITE_NETWORK (default mainnet).
function useBoot() {
  const [boot, setBoot] = useState(null);
  useEffect(() => {
    const lpConfig = Promise.race([lp('/config'), new Promise((_, no) => setTimeout(() => no(new Error('timeout')), 4000))]);
    Promise.all([lpConfig.catch(() => ({})), pickGameApi()]).then(([lpConfig, { api, gameConfig }]) => {
      const network = gameConfig.network || lpConfig.network || import.meta.env.VITE_NETWORK || 'mainnet';
      setBoot({ api, gameConfig, config: { ...lpConfig, ...gameConfig, network, mock: api.mode === 'mock' } });
    });
  }, []);
  return [boot, setBoot];
}

function useInventory(api, address) {
  const [state, setState] = useState({ stacks: null, stats: null, error: '' });
  const reload = useCallback(async () => {
    if (!address) return setState({ stacks: null, stats: null, error: '' });
    try { const r = await api.inventory(address); setState({ stacks: toStacks(r), stats: r.stats || null, error: '' }); }
    catch (e) { setState((s) => ({ ...s, error: e.message })); }
  }, [api, address]);
  useEffect(() => { setState({ stacks: null, stats: null, error: '' }); reload(); }, [reload]);
  useEffect(() => { // live mode: pick up confirmations
    if (!address || api.mode !== 'live') return;
    const id = setInterval(() => !document.hidden && reload(), 30000);
    return () => clearInterval(id);
  }, [api, address, reload]);
  return { ...state, reload };
}

function useRecipes(api, address) {
  const [state, setState] = useState({ recipes: null, error: '' });
  const reload = useCallback(async () => {
    try { setState({ recipes: (await api.recipes(address)).recipes, error: '' }); }
    catch (e) { setState((s) => ({ ...s, error: e.message })); }
  }, [api, address]);
  useEffect(() => { reload(); }, [reload]);
  return { ...state, reload };
}

const cancelled = (e) => e?.code === 4001 || /reject|cancel|denied|user (closed|abort)/i.test(e?.message || '');

function Game({ boot, setBoot }) {
  const { api, config } = boot;
  const [{ page, param }, go] = useRoute();
  const toast = useToast();
  const { t, zh } = useI18n();
  const walletApi = useWallet(config);
  const address = walletApi.wallet?.address;
  const inv = useInventory(api, address);
  const rec = useRecipes(api, address);
  const [tx, setTx] = useState(null);
  const [stamp, setStamp] = useState(0);
  const [pickOpen, setPickOpen] = useState(false);
  const [walletOpen, setWalletOpen] = useState(false);
  const [pendingOpen, setPendingOpen] = useState(false);
  // unconfirmed game transactions: drives the HUD counter, incoming items and reserved inputs
  const pending = usePending({
    api, config, address,
    onConfirmed: (entries) => {
      refreshAll();
      const what = entries.map((e) => (e.kind === 'mint' ? `${e.amount} × ${nameOf(def(e.element), zh).main}` : nameOf(def(e.output), zh).main)).join(', ');
      toast(t('Confirmed on Bitcoin: {what}', { what }), 'ok');
    },
  });

  const connect = () => setPickOpen(true);
  const pick = (id) => {
    setPickOpen(false);
    walletApi.connect(id).then(() => toast(t('Wallet connected'), 'ok')).catch((e) => toast(t(e.message), 'err'));
  };

  async function refreshAll() {
    const gameConfig = await api.config().catch(() => null);
    if (gameConfig) setBoot((b) => ({ ...b, gameConfig, config: { ...b.config, ...gameConfig, network: b.config.network } }));
    inv.reload();
    rec.reload();
    setStamp((s) => s + 1);
  }

  // Shared flow for mint and forge: build → sign → broadcast, with the modal following along.
  async function run(kind, base, build) {
    if (!walletApi.wallet) { connect(); return 'error'; }
    setTx({ state: 'pending', kind, step: 0, mock: api.mode === 'mock', ...base });
    try {
      const fees = api.mode === 'mock' ? null : await feeRates(config);
      const built = await build(fees?.halfHourFee || 2);
      setTx((t) => ({ ...t, step: 1 }));
      let res;
      if (built.mock) {
        await wait(700);
        setTx((t) => ({ ...t, step: 2 }));
        res = await api.broadcast({ built });
      } else {
        const signed = await walletApi.signPsbt(built);
        setTx((t) => ({ ...t, step: 2 }));
        res = await api.broadcast({ ...signed, kind });
      }
      const r = res.result || {};
      const output = r.output || built.preview?.output;
      pending.add({ txid: res.txid, kind, element: base.element, amount: base.amount, inputs: base.inputs, output, confirmAt: res.confirm_at, mock: !!built.mock });
      setTx((t) => ({
        ...t, state: 'ok', txid: res.txid, confirmAt: res.confirm_at,
        output: r.output || built.preview?.output,
        firstEver: !!r.firstEver, firstForYou: !!r.firstForYou,
        costBtc: built.cost_btc, points: r.points,
        royaltyTo: r.royalty_to || built.preview?.royalty_to, royaltyBtc: built.preview?.royalty_btc,
      }));
      refreshAll();
      return 'ok';
    } catch (e) {
      if (e.code === 'NO_RECIPE') { setTx((t) => ({ ...t, state: 'rejected' })); return 'rejected'; }
      if (e.code === 'CAPPED') { setTx((x) => ({ ...x, state: 'error', capped: true, msg: t('This recipe has hit its cap and is closed.') })); refreshAll(); return 'error'; }
      if (e.code === 'NOT_ENOUGH') e.message = "You don't have enough of these ingredients.";
      if (e.code === 'SOLD_OUT') e.message = 'Not enough left to mint that many.';
      setTx((x) => ({ ...x, state: 'error', cancelled: cancelled(e), msg: cancelled(e) ? t('You cancelled in your wallet.') : t(e.message) }));
      return 'error';
    }
  }

  const w = walletApi.wallet;
  // pending_txids lets the backend chain on unconfirmed change and avoid re-spending seals (see client.js)
  const body = (fee) => ({ address: w.address, public_key: w.publicKey, payment: w.payment, fee_rate: fee, pending_txids: pending.txids });
  const onMint = (element, amount) => run('mint', { element, amount },
    (fee) => api.mintPsbt({ ...body(fee), element, amount }));
  const onCraft = (inputs) => run('craft', { inputs },
    (fee) => api.craftPsbt({ ...body(fee), inputs: inputs.map((id) => ({ id })) }));

  const closeTx = () => setTx(null);
  // leaving the page (e.g. browser back) dismisses a finished result
  useEffect(() => { setTx((t) => (t && t.state !== 'pending' ? null : t)); }, [page]);
  const txActions = !tx || tx.state === 'pending' ? []
    : tx.state === 'ok' && tx.kind === 'mint' ? [{ label: 'Mint more', onClick: closeTx }, { label: 'Go to the forge', primary: true, onClick: () => { closeTx(); go('forge'); } }]
    : tx.state === 'ok' ? [{ label: 'View in bag', onClick: () => { closeTx(); go('inventory'); } }, { label: 'Forge again', primary: true, onClick: closeTx }]
    : tx.state === 'rejected' ? [{ label: 'Try another mix', primary: true, onClick: closeTx }]
    : [{ label: 'Close', primary: true, onClick: closeTx }];

  const shared = { walletApi, onConnect: connect, inv, pending };
  const screen = page === 'mint' ? <Mint gameConfig={boot.gameConfig} onMint={onMint} busy={tx?.state === 'pending'} pending={pending} />
    : page === 'inventory' ? <Inventory {...shared} season={boot.gameConfig.season} />
    : page === 'forge' ? <Forge {...shared} season={boot.gameConfig.season} myPoints={inv.stats?.season_points || 0} recipes={rec.recipes} param={param} onCraft={onCraft} firing={tx?.state === 'pending' && tx.kind === 'craft'} rejected={tx?.state === 'rejected'} />
    : page === 'recipes' ? <Recipes recipes={rec.recipes} error={rec.error} reload={rec.reload} me={address} />
    : page === 'leaderboard' ? <Leaderboard api={api} me={address} stamp={stamp} myPoints={inv.stats?.season_points || 0} />
    : page === 'market' ? <Market />
    : page === 'about' ? <About />
    : <Home api={api} walletApi={walletApi} onConnect={connect} recipes={rec.recipes} gameConfig={boot.gameConfig} stamp={stamp} />;

  const resetPreview = () => { resetMock(); try { Object.keys(localStorage).filter((k) => k.startsWith('hf.pending.')).forEach((k) => localStorage.removeItem(k)); } catch (_) {} location.reload(); };

  // Console layout: HUD on top, the screen (the only scrolling area) in the middle, controls docked
  // at the bottom on phones or in a rail on the left on desktop. Nothing floats over the game.
  return <div className={`app shell page-${page}`}>
    <SealDefs />
    <Backdrop />
    <TopBar page={page} config={config} walletApi={walletApi} onConnect={connect} onOpenWallet={() => setWalletOpen(true)}
      mock={api.mode === 'mock'} pending={pending} onOpenPending={() => setPendingOpen(true)} />
    <div className="shell-body">
      <Rail page={page} mock={api.mode === 'mock'} onReset={resetPreview} />
      <main className="screen" id="screen">
        <div className="wrap page" key={page}>{screen}</div>
      </main>
    </div>
    <Dock page={page} />
    <PendingSheet open={pendingOpen} onClose={() => setPendingOpen(false)} pending={pending} config={config} mock={api.mode === 'mock'} />
    <WalletPicker open={pickOpen} onClose={() => setPickOpen(false)} config={config} onPick={pick} mock={api.mode === 'mock'} />
    <WalletSheet open={walletOpen} onClose={() => setWalletOpen(false)} walletApi={walletApi} config={config} inv={inv}
      pending={pending} onOpenPending={() => { setWalletOpen(false); setPendingOpen(true); }} mock={api.mode === 'mock'} onReset={resetPreview} />
    <ActionModal tx={tx} config={config} me={address} myPoints={inv.stats?.season_points || 0} walletName={walletApi.walletName} onClose={closeTx} actions={txActions} />
  </div>;
}

function App() {
  const [boot, setBoot] = useBoot();
  if (!boot) return <Boot />;
  return <ToastProvider><Game boot={boot} setBoot={setBoot} /></ToastProvider>;
}

function Boot() {
  const { zh } = useI18n();
  return <div className="boot">
    <span className="boot-art"><span className="core-glow" aria-hidden="true" /><LogoMark size={112} /></span>
    <p className="boot-title">天工炉</p>
    <span className="loading" aria-hidden="true"><i /></span>
    <p>{zh ? '炉火渐旺…' : 'Lighting the forge…'}</p>
  </div>;
}

createRoot(document.getElementById('root')).render(<LangProvider><App /></LangProvider>);
