'use client';

/**
 * /deploy-7702 — one-shot deploy page for VaultfireBatchOnboarder.
 *
 * This page is a single-purpose tool: connect the deployer wallet
 * (ghostkey.base.eth = 0xfA15...813C) and broadcast the contract creation tx.
 * Once deployed, this page is no longer needed and can be removed (or left
 * up as a record of what was deployed).
 *
 * Safety:
 *   - The deployer address is hardcoded; if a different wallet connects,
 *     the deploy button is disabled.
 *   - The bytecode keccak256 is shown so the user can verify it matches
 *     what they expect from the source repo.
 *   - The chain id is checked against Base mainnet (8453) before sending.
 *   - No off-chain key handling, no relayers — the user signs in their own
 *     wallet (Base App / Coinbase Wallet / etc.).
 */

import { useEffect, useMemo, useState } from 'react';
import {
  VAULTFIRE_ONBOARDER_BYTECODE,
  EXPECTED_BYTECODE_KECCAK256,
  DEPLOYER_ADDRESS,
  PREDICTED_DEPLOY_ADDRESS,
  BASE_CHAIN_ID,
  VAULTFIRE_TARGETS,
  ARACHNID_CREATE2_FACTORY,
  DEPLOY_SALT,
} from './bytecode';

// Minimal ethereum provider type — avoids pulling in wagmi for one page.
interface EthProvider {
  request: (args: { method: string; params?: unknown[] }) => Promise<unknown>;
  on?: (event: string, fn: (...args: unknown[]) => void) => void;
  removeListener?: (event: string, fn: (...args: unknown[]) => void) => void;
  isCoinbaseWallet?: boolean;
  isMetaMask?: boolean;
}

// NOTE: a global `Window.ethereum` declaration already exists elsewhere in
// the codebase (see app/lib/forum-xmtp.ts). Re-declaring here would conflict
// during type merging, so this file casts `window.ethereum` to the richer
// EthProvider locally where needed.
function getEth(): EthProvider | undefined {
  if (typeof window === 'undefined') return undefined;
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  return (window as any).ethereum as EthProvider | undefined;
}

type Status =
  | { kind: 'idle' }
  | { kind: 'connecting' }
  | { kind: 'estimating' }
  | { kind: 'awaitingSignature' }
  | { kind: 'broadcasting'; hash: string }
  | { kind: 'mined'; hash: string; address: string }
  | { kind: 'error'; message: string };

function short(addr: string | null) {
  return addr ? `${addr.slice(0, 6)}…${addr.slice(-4)}` : '';
}

function eq(a: string | null | undefined, b: string | null | undefined) {
  return !!a && !!b && a.toLowerCase() === b.toLowerCase();
}

async function rpc<T>(method: string, params: unknown[] = []): Promise<T> {
  const eth = getEth();
  if (!eth) throw new Error('No injected wallet detected.');
  return eth.request({ method, params }) as Promise<T>;
}

export default function DeployClient() {
  const [address, setAddress] = useState<string | null>(null);
  const [chainId, setChainId] = useState<number | null>(null);
  const [status, setStatus] = useState<Status>({ kind: 'idle' });

  // Restore connection on mount
  useEffect(() => {
    const eth = getEth();
    if (!eth) return;
    eth.request({ method: 'eth_accounts' })
      .then((accs) => {
        const arr = accs as string[];
        if (arr?.[0]) setAddress(arr[0]);
      })
      .catch(() => {});
    eth.request({ method: 'eth_chainId' })
      .then((cid) => setChainId(parseInt(cid as string, 16)))
      .catch(() => {});
    const onAcc = (...args: unknown[]) => {
      const accs = args[0] as string[];
      setAddress(accs?.[0] ?? null);
    };
    const onChain = (...args: unknown[]) => {
      setChainId(parseInt(args[0] as string, 16));
    };
    eth.on?.('accountsChanged', onAcc);
    eth.on?.('chainChanged', onChain);
    return () => {
      eth.removeListener?.('accountsChanged', onAcc);
      eth.removeListener?.('chainChanged', onChain);
    };
  }, []);

  const isDeployer = eq(address, DEPLOYER_ADDRESS);
  const isBase = chainId === BASE_CHAIN_ID;

  // Compute keccak256 of bytecode in browser (sanity check that what we serve
  // matches the expected hash). Uses Web Crypto for SHA-256 only; for keccak256
  // we trust the constant since computing it client-side adds a heavy dep.
  // We expose the constant for the user to verify against `cast keccak`.
  const bytecodePreview = useMemo(() => {
    const b = VAULTFIRE_ONBOARDER_BYTECODE;
    return `${b.slice(0, 30)}…${b.slice(-26)} (${(b.length - 2) / 2} bytes)`;
  }, []);

  async function connect() {
    setStatus({ kind: 'connecting' });
    try {
      const accs = await rpc<string[]>('eth_requestAccounts');
      setAddress(accs[0] ?? null);
      const cid = await rpc<string>('eth_chainId');
      setChainId(parseInt(cid, 16));
      setStatus({ kind: 'idle' });
    } catch (e) {
      setStatus({ kind: 'error', message: (e as Error).message ?? 'Connect failed' });
    }
  }

  async function switchToBase() {
    try {
      await rpc('wallet_switchEthereumChain', [{ chainId: '0x2105' }]);
    } catch (e) {
      // Try add chain on rejection (4902 = unrecognized)
      const code = (e as { code?: number }).code;
      if (code === 4902) {
        await rpc('wallet_addEthereumChain', [{
          chainId: '0x2105',
          chainName: 'Base',
          nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
          rpcUrls: ['https://mainnet.base.org'],
          blockExplorerUrls: ['https://basescan.org'],
        }]);
      } else {
        setStatus({ kind: 'error', message: (e as Error).message ?? 'Switch failed' });
      }
    }
  }

  async function deploy() {
    if (!address || !isDeployer || !isBase) return;
    setStatus({ kind: 'estimating' });
    try {
      // Arachnid CREATE2 factory expects raw calldata = salt(32) ++ initcode.
      // It deploys via CREATE2 and the resulting address is deterministic from
      // (factory, salt, keccak256(initcode)) — independent of msg.sender.
      // Crucially, this is a normal `to: factory` tx (not a `to: null` contract
      // creation), so it works through ANY wallet including Base App / Coinbase
      // Smart Wallet that don't support raw deploys.
      const calldata = (DEPLOY_SALT + VAULTFIRE_ONBOARDER_BYTECODE.slice(2)) as `0x${string}`;

      // Estimate gas through the factory.
      const gasHex = await rpc<string>('eth_estimateGas', [{
        from: address,
        to: ARACHNID_CREATE2_FACTORY,
        data: calldata,
      }]);
      const gas = BigInt(gasHex);
      const gasLimit = (gas * 125n) / 100n; // 25% buffer
      const gasLimitHex = '0x' + gasLimit.toString(16);

      setStatus({ kind: 'awaitingSignature' });
      const txHash = await rpc<string>('eth_sendTransaction', [{
        from: address,
        to: ARACHNID_CREATE2_FACTORY,
        data: calldata,
        gas: gasLimitHex,
      }]);
      setStatus({ kind: 'broadcasting', hash: txHash });

      // Poll for receipt. The factory's CREATE2 deploys at the predicted
      // address; the receipt itself won't show contractAddress (because the
      // outer tx targets the factory, not a creation), so we use the predicted
      // address as the source of truth and verify via eth_getCode below.
      for (let i = 0; i < 90; i++) {
        const receipt = await rpc<{ status?: string; blockNumber?: string } | null>(
          'eth_getTransactionReceipt',
          [txHash],
        );
        if (receipt) {
          if (receipt.status === '0x0') {
            setStatus({ kind: 'error', message: `Tx ${txHash} reverted on-chain.` });
            return;
          }
          // Verify the predicted address actually has code now.
          const code = await rpc<string>('eth_getCode', [PREDICTED_DEPLOY_ADDRESS, 'latest']);
          if (code && code !== '0x') {
            setStatus({ kind: 'mined', hash: txHash, address: PREDICTED_DEPLOY_ADDRESS });
            return;
          }
          setStatus({
            kind: 'error',
            message: `Tx mined but no code found at ${PREDICTED_DEPLOY_ADDRESS}. Already deployed at this address?`,
          });
          return;
        }
        await new Promise((r) => setTimeout(r, 2000));
      }
      setStatus({
        kind: 'error',
        message: `Tx ${txHash} not yet mined after 3 minutes. Check Basescan; it may still confirm.`,
      });
    } catch (e) {
      const msg = (e as Error).message ?? String(e);
      setStatus({ kind: 'error', message: msg });
    }
  }

  // ─── Render ─────────────────────────────────────────────────────

  return (
    <div className="min-h-screen bg-black text-white">
      <div className="mx-auto max-w-3xl px-5 py-12">
        <div className="mb-8 flex flex-wrap items-center gap-4">
          <a href="/" className="text-sm text-white/50 hover:text-white/80">
            ← theloopbreaker.com
          </a>
          {/* Read-only V3 status console. Adds no action to this page. */}
          <a href="/sign/v3-launch" className="text-sm text-white/50 hover:text-white/80">
            V3 launch console (read-only, locked) →
          </a>
        </div>

        <h1 className="text-3xl md:text-4xl font-semibold mb-2">
          Deploy VaultfireBatchOnboarder
        </h1>
        <p className="text-white/60 mb-8">
          One-shot deploy page for the EIP-7702 batch onboarder contract.
          Base mainnet only. Owner-gated to ghostkey.base.eth.
        </p>

        <Section title="What this deploys">
          <Row label="Contract" value="VaultfireBatchOnboarder" mono />
          <Row label="Source" value="github.com/ghostkey-316/vaultfire (sprint-7702)" mono />
          <Row label="Compiler" value="solc 0.8.24 · viaIR · optimizer 200 · cancun" mono />
          <Row label="Bytecode" value={bytecodePreview} mono />
          <Row label="Bytecode keccak256" value={EXPECTED_BYTECODE_KECCAK256} mono small />
        </Section>

        <Section title="Deploy method (CREATE2 via Arachnid factory)">
          <Row label="Factory" value={ARACHNID_CREATE2_FACTORY} mono small />
          <Row label="Salt" value={DEPLOY_SALT} mono small />
          <Row label="Predicted address" value={PREDICTED_DEPLOY_ADDRESS} mono small />
          <div className="pt-2 text-xs text-white/50">
            Going through the canonical CREATE2 factory means this works in any wallet,
            including smart-wallet UIs like Base App. The address is identical regardless
            of which wallet signs.
          </div>
        </Section>

        <Section title="Targets baked in (Base mainnet)">
          <Row label="IdentityRegistry" value={VAULTFIRE_TARGETS.identityRegistry} mono small />
          <Row label="PartnershipBondsV2" value={VAULTFIRE_TARGETS.partnershipBondsV2} mono small />
          <Row label="Min stake" value="1 wei (matches on-chain require)" />
          <Row label="Chain id" value={`${BASE_CHAIN_ID} (Base mainnet)`} />
        </Section>

        <Section title="Wallet">
          {!address ? (
            <button
              onClick={connect}
              className="w-full rounded-lg bg-white text-black font-medium px-5 py-3 hover:bg-white/90 transition"
            >
              Connect wallet
            </button>
          ) : (
            <>
              <Row label="Connected" value={short(address)} mono />
              <Row
                label="Address match"
                value={isDeployer ? '✓ Deployer wallet' : '✗ Wrong wallet'}
                tone={isDeployer ? 'good' : 'bad'}
              />
              <Row
                label="Chain"
                value={isBase ? '✓ Base (8453)' : `✗ ${chainId ?? 'unknown'}`}
                tone={isBase ? 'good' : 'bad'}
              />
              {!isBase && (
                <button
                  onClick={switchToBase}
                  className="mt-3 w-full rounded-lg bg-blue-500 text-white font-medium px-5 py-3 hover:bg-blue-600 transition"
                >
                  Switch to Base
                </button>
              )}
            </>
          )}
        </Section>

        <Section title="Deploy">
          <button
            onClick={deploy}
            disabled={!isDeployer || !isBase || status.kind === 'awaitingSignature' || status.kind === 'broadcasting' || status.kind === 'estimating'}
            className="w-full rounded-lg bg-orange-500 text-white font-semibold px-5 py-4 text-lg hover:bg-orange-600 transition disabled:opacity-40 disabled:cursor-not-allowed"
          >
            {status.kind === 'estimating' && 'Estimating gas…'}
            {status.kind === 'awaitingSignature' && 'Awaiting signature…'}
            {status.kind === 'broadcasting' && 'Broadcasting…'}
            {status.kind === 'idle' && 'Deploy contract'}
            {status.kind === 'connecting' && 'Connecting…'}
            {status.kind === 'mined' && 'Deployed ✓'}
            {status.kind === 'error' && 'Retry deploy'}
          </button>

          {status.kind === 'broadcasting' && (
            <div className="mt-4 text-sm text-white/70">
              Tx submitted:{' '}
              <a
                href={`https://basescan.org/tx/${status.hash}`}
                target="_blank"
                rel="noreferrer"
                className="underline"
              >
                {status.hash.slice(0, 10)}…
              </a>
            </div>
          )}

          {status.kind === 'mined' && (
            <div className="mt-6 rounded-lg border border-green-500/40 bg-green-500/10 p-4">
              <div className="font-semibold text-green-300 mb-2">Contract deployed</div>
              <div className="text-sm space-y-1 text-white/80">
                <div>
                  Address:{' '}
                  <a
                    href={`https://basescan.org/address/${status.address}#code`}
                    target="_blank"
                    rel="noreferrer"
                    className="font-mono underline"
                  >
                    {status.address}
                  </a>
                </div>
                <div>
                  Tx:{' '}
                  <a
                    href={`https://basescan.org/tx/${status.hash}`}
                    target="_blank"
                    rel="noreferrer"
                    className="font-mono underline"
                  >
                    {status.hash}
                  </a>
                </div>
                <div className="pt-2 text-white/60 text-xs">
                  Next: verify source on Basescan, then run a fresh-wallet
                  one-signature onboard from the mini-app.
                </div>
              </div>
            </div>
          )}

          {status.kind === 'error' && (
            <div className="mt-4 rounded-lg border border-red-500/40 bg-red-500/10 p-4 text-sm text-red-300">
              {status.message}
            </div>
          )}
        </Section>

        <p className="mt-12 text-xs text-white/40">
          Verify the bytecode locally before signing:{' '}
          <code className="font-mono">cast keccak &quot;$(cat DEPLOY_BYTECODE.hex)&quot;</code>{' '}
          should equal the keccak256 above.
        </p>
      </div>
    </div>
  );
}

// ─── Building blocks ─────────────────────────────────────────────

function Section({ title, children }: { title: string; children: React.ReactNode }) {
  return (
    <section className="mb-8 rounded-xl border border-white/10 bg-white/[0.02] p-5">
      <h2 className="text-sm font-semibold uppercase tracking-wider text-white/60 mb-4">
        {title}
      </h2>
      <div className="space-y-2">{children}</div>
    </section>
  );
}

function Row({
  label,
  value,
  mono,
  small,
  tone,
}: {
  label: string;
  value: string;
  mono?: boolean;
  small?: boolean;
  tone?: 'good' | 'bad';
}) {
  const valColor =
    tone === 'good' ? 'text-green-400' : tone === 'bad' ? 'text-red-400' : 'text-white/90';
  return (
    <div className="flex items-start gap-3 text-sm">
      <div className="w-40 shrink-0 text-white/50">{label}</div>
      <div
        className={`min-w-0 break-all ${valColor} ${mono ? 'font-mono' : ''} ${small ? 'text-xs' : ''}`}
      >
        {value}
      </div>
    </div>
  );
}
