<template>
  <div class="local-swap-widget">
    <template v-if="!canSwap">
      <p class="swap-warning">Connect your wallet to swap ETH for $KRK on the local Uniswap pool.</p>
    </template>
    <template v-else>
      <div class="swap-field">
        <label for="local-swap-amount" class="swap-label">ETH to spend</label>
        <input id="local-swap-amount" v-model="swapAmount" type="number" min="0" step="0.01" class="swap-input" :disabled="swapping" />
      </div>
      <button class="swap-button" :disabled="swapping" @click="buyKrk">
        {{ swapping ? 'Submitting…' : 'Buy KRK' }}
      </button>
      <p class="swap-hint">Wraps ETH → WETH, approves the swap router, then trades into $KRK.</p>
    </template>
  </div>
</template>

<script setup lang="ts">
import { computed, ref } from 'vue';
import { useAccount } from '@wagmi/vue';
import { readContract, writeContract } from '@wagmi/core';
import { erc20Abi, getAddress, isAddress, maxUint256, parseEther, zeroAddress, type Abi, type Address } from 'viem';
import { useToast } from 'vue-toastification';
import { config as wagmiConfig } from '@/wagmi';
import { getChain, DEFAULT_CHAIN_ID } from '@/config';
import { useWallet } from '@/composables/useWallet';

const toast = useToast();
const { address, chainId } = useAccount();
const { loadBalance } = useWallet();

const swapAmount = ref('0.1');
const swapping = ref(false);

const resolvedChainId = computed(() => chainId.value ?? DEFAULT_CHAIN_ID);
const chainConfig = computed(() => getChain(resolvedChainId.value));
const cheatConfig = computed(() => chainConfig.value?.cheats ?? null);

const canSwap = computed(() =>
  Boolean(address.value && cheatConfig.value?.weth && cheatConfig.value?.swapRouter && chainConfig.value?.harb)
);

const WETH_ABI = [
  {
    inputs: [],
    name: 'deposit',
    outputs: [],
    stateMutability: 'payable',
    type: 'function',
  },
] as const satisfies Abi;

const SWAP_ROUTER_ABI = [
  {
    inputs: [],
    name: 'factory',
    outputs: [{ internalType: 'address', name: '', type: 'address' }],
    stateMutability: 'view',
    type: 'function',
  },
  {
    inputs: [
      {
        components: [
          { internalType: 'address', name: 'tokenIn', type: 'address' },
          { internalType: 'address', name: 'tokenOut', type: 'address' },
          { internalType: 'uint24', name: 'fee', type: 'uint24' },
          { internalType: 'address', name: 'recipient', type: 'address' },
          { internalType: 'uint256', name: 'amountIn', type: 'uint256' },
          { internalType: 'uint256', name: 'amountOutMinimum', type: 'uint256' },
          { internalType: 'uint160', name: 'sqrtPriceLimitX96', type: 'uint160' },
        ],
        internalType: 'struct ISwapRouter.ExactInputSingleParams',
        name: 'params',
        type: 'tuple',
      },
    ],
    name: 'exactInputSingle',
    outputs: [{ internalType: 'uint256', name: 'amountOut', type: 'uint256' }],
    stateMutability: 'payable',
    type: 'function',
  },
] as const satisfies Abi;

const UNISWAP_FACTORY_ABI = [
  {
    inputs: [
      { internalType: 'address', name: 'tokenA', type: 'address' },
      { internalType: 'address', name: 'tokenB', type: 'address' },
      { internalType: 'uint24', name: 'fee', type: 'uint24' },
    ],
    name: 'getPool',
    outputs: [{ internalType: 'address', name: 'pool', type: 'address' }],
    stateMutability: 'view',
    type: 'function',
  },
] as const satisfies Abi;

const UNISWAP_POOL_ABI = [
  {
    inputs: [],
    name: 'liquidity',
    outputs: [{ internalType: 'uint128', name: '', type: 'uint128' }],
    stateMutability: 'view',
    type: 'function',
  },
] as const satisfies Abi;

function ensureAddress(value: string, label: string): Address {
  if (!value || !isAddress(value)) {
    throw new Error(`${label} is not a valid address`);
  }
  return getAddress(value);
}

function isRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === 'object' && value !== null;
}

function coerceString(value: unknown): string | null {
  if (typeof value === 'string') {
    const trimmed = value.trim();
    if (trimmed.length > 0) return trimmed;
  }
  return null;
}

function getErrorMessage(error: unknown, fallback: string): string {
  if (error instanceof Error) {
    const msg = coerceString(error.message);
    if (msg) return msg;
  }
  if (isRecord(error)) {
    const short = coerceString(error.shortMessage);
    if (short) return short;
    const msg = coerceString(error.message);
    if (msg) return msg;
  }
  return fallback;
}

async function buyKrk() {
  if (!canSwap.value || swapping.value) return;
  try {
    swapping.value = true;

    const chain = chainConfig.value;
    const cheat = cheatConfig.value;

    if (!chain?.harb) throw new Error('No Kraiken deployment configured for this chain');
    if (!cheat) throw new Error('Swap helpers are not configured for this chain');

    const weth = ensureAddress(cheat.weth, 'WETH');
    const router = ensureAddress(cheat.swapRouter, 'Swap router');
    const harb = ensureAddress(chain.harb, 'KRAIKEN token');
    const caller = ensureAddress(address.value!, 'Wallet address');
    const currentChainId = resolvedChainId.value;

    let amount: bigint;
    try {
      amount = parseEther(swapAmount.value || '0');
    } catch {
      throw new Error('Enter a valid ETH amount');
    }
    if (amount <= 0n) throw new Error('Amount must be greater than zero');

    const factoryAddress = await readContract(wagmiConfig, {
      abi: SWAP_ROUTER_ABI,
      address: router,
      functionName: 'factory',
      chainId: currentChainId,
    });
    const factory = ensureAddress(factoryAddress, 'Uniswap factory');

    const poolAddress = await readContract(wagmiConfig, {
      abi: UNISWAP_FACTORY_ABI,
      address: factory,
      functionName: 'getPool',
      args: [weth, harb, 10_000],
      chainId: currentChainId,
    });
    if (!poolAddress || poolAddress === zeroAddress) {
      throw new Error('No KRK/WETH pool found at 1% fee; deploy and recenter first');
    }

    const poolLiquidity = await readContract(wagmiConfig, {
      abi: UNISWAP_POOL_ABI,
      address: poolAddress,
      functionName: 'liquidity',
      chainId: currentChainId,
    });
    if (poolLiquidity === 0n) {
      throw new Error('KRK/WETH pool has zero liquidity; run recenter before swapping');
    }

    const wethBalance = await readContract(wagmiConfig, {
      abi: erc20Abi,
      address: weth,
      functionName: 'balanceOf',
      args: [caller],
      chainId: currentChainId,
    });

    const wrapAmount = amount > wethBalance ? amount - wethBalance : 0n;
    if (wrapAmount > 0n) {
      await writeContract(wagmiConfig, {
        abi: WETH_ABI,
        address: weth,
        functionName: 'deposit',
        value: wrapAmount,
        chainId: currentChainId,
      });
    }

    const allowance = await readContract(wagmiConfig, {
      abi: erc20Abi,
      address: weth,
      functionName: 'allowance',
      args: [caller, router],
      chainId: currentChainId,
    });
    if (allowance < amount) {
      await writeContract(wagmiConfig, {
        abi: erc20Abi,
        address: weth,
        functionName: 'approve',
        args: [router, maxUint256],
        chainId: currentChainId,
      });
    }

    await writeContract(wagmiConfig, {
      abi: SWAP_ROUTER_ABI,
      address: router,
      functionName: 'exactInputSingle',
      args: [
        {
          tokenIn: weth,
          tokenOut: harb,
          fee: 10_000,
          recipient: caller,
          amountIn: amount,
          amountOutMinimum: 0n, // no slippage protection — acceptable on no-MEV local anvil
          sqrtPriceLimitX96: 0n,
        },
      ],
      chainId: currentChainId,
    });

    await loadBalance();

    toast.success('Swap complete. $KRK has been added to your wallet.');
  } catch (error: unknown) {
    toast.error(getErrorMessage(error, 'Swap failed'));
  } finally {
    swapping.value = false;
  }
}
</script>

<style lang="sass" scoped>
.local-swap-widget
  display: flex
  flex-direction: column
  gap: 16px

.swap-warning
  margin: 0
  color: #FFB347
  font-size: 14px

.swap-label
  display: block
  font-size: 13px
  color: #a3a3a3
  margin-bottom: 6px

.swap-field
  display: flex
  flex-direction: column

.swap-input
  background: #111111
  border: 1px solid #3a3a3a
  border-radius: 8px
  color: #ffffff
  font-size: 16px
  padding: 12px 16px
  outline: none
  transition: border-color 0.2s

  &:focus
    border-color: #60a5fa

  &:disabled
    opacity: 0.5
    cursor: not-allowed

.swap-button
  display: flex
  align-items: center
  justify-content: center
  background: linear-gradient(135deg, #60a5fa 0%, #3b82f6 100%)
  color: #ffffff
  font-size: 16px
  font-weight: 600
  padding: 16px 32px
  border-radius: 12px
  border: none
  cursor: pointer
  transition: all 0.3s ease
  box-shadow: 0 4px 12px rgba(96, 165, 250, 0.3)

  &:hover:not(:disabled)
    transform: translateY(-2px)
    box-shadow: 0 6px 16px rgba(96, 165, 250, 0.4)
    background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%)

  &:disabled
    opacity: 0.6
    cursor: not-allowed
    transform: none

.swap-hint
  margin: 0
  font-size: 12px
  color: #a3a3a3
</style>
