'use client';

import { useEffect, useMemo, useState } from 'react';
import { useAccount, useChainId, useReadContract, useReadContracts, useSwitchChain, usePublicClient, useWriteContract, useSendTransaction } from 'wagmi';
import { base, arbitrum, mainnet } from 'wagmi/chains';
import { Abi, parseUnits } from 'viem';
import debtAbi from '@/ABIs/mortgagefiusdccbbtcupgraded.json';
import Link from 'next/link';
import { useSearchParams } from 'next/navigation';
import SettingsModal from '@/components/SettingsModal';
import { useLocalStorage } from '@/utils/useLocalStorage';
import type { NotificationSettings, PositionNotification } from '@/types/notifications';
import { scheduleJob, cancelScheduledJob, purgeNtfyTopicSchedules } from '@/utils/scheduler';

// Minimal ERC-721 ABI for balance/owner/enumeration
const erc721Abi = [
  { type: 'function', name: 'balanceOf', stateMutability: 'view', inputs: [{ name: 'owner', type: 'address' }], outputs: [{ type: 'uint256' }] },
  { type: 'function', name: 'ownerOf', stateMutability: 'view', inputs: [{ name: 'tokenId', type: 'uint256' }], outputs: [{ type: 'address' }] },
  { type: 'function', name: 'supportsInterface', stateMutability: 'view', inputs: [{ name: 'interfaceId', type: 'bytes4' }], outputs: [{ type: 'bool' }] },
  // ERC721Enumerable
  { type: 'function', name: 'tokenOfOwnerByIndex', stateMutability: 'view', inputs: [{ name: 'owner', type: 'address' }, { name: 'index', type: 'uint256' }], outputs: [{ type: 'uint256' }] },
] as const satisfies Abi;

const ENABLE_MAINNET = process.env.NEXT_PUBLIC_ENABLE_MAINNET === 'true';

const DEFAULTS = {
  [base.id]: {
    nft: '0xcc9a350c5b1e1c9ecd23d376e6618cdfd6bbbdbe',
    debt: '0xe93131620945a1273b48f57f453983d270b62dc7',
  },
  [arbitrum.id]: {
    nft: '0xedE6F5F8A9D6B90b1392Dcc9E7FD8A5B0192Bfe1',
    debt: '0x9Be2Cf73E62DD3b5dF4334D9A36888394822A33F',
  },
  ...(ENABLE_MAINNET ? {
    [mainnet.id]: {
      // Dummy placeholders for future USDC-WETH mainnet vault
      nft: '0x0000000000000000000000000000000000000001',
      debt: '0x0000000000000000000000000000000000000002',
    },
  } : {}),
} as const;

// Presets per chain (selectable pairs)
const PRESETS: Partial<Record<number, { key: string; label: string; nft: string; debt: string }[]>> = {
  [base.id]: [
    { key: 'cbBTC-USDC', label: 'cbBTC-USDC', nft: '0xcc9a350c5b1e1c9ecd23d376e6618cdfd6bbbdbe', debt: '0xe93131620945a1273b48f57f453983d270b62dc7' },
    { key: 'WETH-USDC', label: 'WETH-USDC', nft: '0xab825f45e9e5d2459fb7a1527a8d0ca082c582f4', debt: '0x1be87d273d47c3832ab7853812e9a995a4de9eea' },
  ],
  [arbitrum.id]: [
    { key: 'USDTO-WBTC', label: 'USDTO-WBTC', nft: '0xedE6F5F8A9D6B90b1392Dcc9E7FD8A5B0192Bfe1', debt: '0x9Be2Cf73E62DD3b5dF4334D9A36888394822A33F' },
  ],
  ...(ENABLE_MAINNET ? {
    [mainnet.id]: [
      { key: 'USDC-WETH', label: 'USDC-WETH (mainnet dummy)', nft: '0x0000000000000000000000000000000000000001', debt: '0x0000000000000000000000000000000000000002' },
    ],
  } : {}),
};

export default function DappPage() {
  const { address, isConnected } = useAccount();
  const chainId = useChainId();
  const { switchChain } = useSwitchChain();
  const searchParams = useSearchParams();

  const [selectedChainId, setSelectedChainId] = useState<number>(base.id);
  const [nftAddress, setNftAddress] = useState<string>(DEFAULTS[base.id].nft);
  const [debtAddress, setDebtAddress] = useState<string>(DEFAULTS[base.id].debt);
  const [presetKey, setPresetKey] = useState<string>('cbBTC-USDC');
  const [manualWallet, setManualWallet] = useState<string>('');
  const [manualTokenId, setManualTokenId] = useState('');
  const [deeplinkTokenId, setDeeplinkTokenId] = useState('');
  const [detectedTokenIds, setDetectedTokenIds] = useState<bigint[]>([]);
  const [scanBusy, setScanBusy] = useState(false);
  const [scanComplete, setScanComplete] = useState(false);
  const [payInputs, setPayInputs] = useState<Record<string, string>>({});
  const publicClient = usePublicClient({ chainId: selectedChainId });
  const { writeContractAsync, isPending: writePending } = useWriteContract();
  const { sendTransactionAsync, isPending: txPending } = useSendTransaction();

  // Notification settings (global) and per-position preferences
  const [settingsOpen, setSettingsOpen] = useState(false);

  useEffect(() => {
    if (searchParams?.get('settings') === '1') setSettingsOpen(true);
  }, [searchParams]);
  const ENV_NTFY = (process.env.NEXT_PUBLIC_NTFY_URL || process.env.NTFY_URL || 'https://ntfy.sh').replace(/\/$/, '');
  const ENV_SCHEDY = (process.env.NEXT_PUBLIC_SCHEDY_URL || process.env.SCHEDY_URL || 'http://localhost:8080').replace(/\/$/, '');
  const ENV_SCHEDY_API_KEY = (process.env.NEXT_PUBLIC_SCHEDY_API_KEY || process.env.SCHEDY_API_KEY || '').trim();
  const [notifSettings, setNotifSettings] = useLocalStorage<NotificationSettings>('notif:settings', {
    provider: '',
    scheduler: 'schedy',
    ntfyServer: ENV_NTFY,
    ntfyTopic: '',
    gotifyServer: '',
    gotifyToken: '',
    snsRegion: '',
    snsTopicArn: '',
    snsAccessKeyId: '',
    snsSecretAccessKey: '',
    schedyBaseUrl: ENV_SCHEDY,
    schedyApiKey: ENV_SCHEDY_API_KEY,
    email: '',
    backupEmail: '',
    backupDelayDays: 1,
    daysBefore: 10,
  });
  useEffect(() => {
    console.log('[Settings] Current', notifSettings);
  }, [notifSettings]);

  // One-time migration: minutesBefore -> daysBefore
  useEffect(() => {
    const anySettings: any = notifSettings as any;
    if (anySettings && anySettings.minutesBefore !== undefined && anySettings.daysBefore === undefined) {
      const mins = Number(anySettings.minutesBefore) || 0;
      const days = Math.max(0, Math.round(mins / 1440));
      const next = { ...notifSettings, daysBefore: days } as NotificationSettings;
      delete (next as any).minutesBefore;
      console.log('[Settings] Migrating minutesBefore -> daysBefore', { mins, days });
      setNotifSettings(next);
    }
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  // One-time migration: legacy scheduler providers -> schedy; remove legacy fields
  useEffect(() => {
    const s: any = notifSettings as any;
    if (!s) return;
    let changed = false;
    const next: any = { ...notifSettings };
    // Normalize scheduler
    if (!s.scheduler || s.scheduler !== 'schedy') {
      next.scheduler = 'schedy';
      changed = true;
    }
    // Remove legacy keys if present
    const legacyKeys = ['cronhostApiKey', 'kronosBaseUrl', 'schedifyBaseUrl', 'schedifyApiKey', 'schedifyWebhookId'];
    for (const k of legacyKeys) {
      if (k in next) {
        delete next[k];
        changed = true;
      }
    }
    if (changed) {
      console.log('[Settings] Migrating legacy scheduler -> schedy and pruning legacy fields');
      setNotifSettings(next as NotificationSettings);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);
  const positionsStoreKey = useMemo(() => `notif:positions:v1:${selectedChainId}:${debtAddress?.toLowerCase()}`, [selectedChainId, debtAddress]);
  const [positionsNotif, setPositionsNotif] = useLocalStorage<Record<string, PositionNotification>>(positionsStoreKey, {});

  // Cache helpers
  type WalletCache = {
    lastScannedIndex: number;
    tokenIds: string[];
    balance?: string | null;
    updatedAt: number;
    complete?: boolean; // when true, stop further ownerOf scans (gap limit reached)
  };

  // Consume deep-link payload from /dapp/position/[network]/[preset]/[tokenId]
  useEffect(() => {
    try {
      if (typeof window === 'undefined') return;
      const raw = localStorage.getItem('dapp:deeplink:v1');
      if (!raw) return;
      localStorage.removeItem('dapp:deeplink:v1');
      const parsed = JSON.parse(raw || '{}') as { network?: string; preset?: number; tokenId?: string; ts?: number };
      const age = Math.floor(Date.now() / 1000) - (parsed.ts || 0);
      if (age > 600) return; // ignore stale links >10min
      const net = String(parsed.network || '').toUpperCase();
      const presetIdx = Math.max(1, Number(parsed.preset || 1));
      const nextChainId = net === 'BASE' ? base.id
        : ((net === 'ARBITRUM' || net === 'ARB') ? arbitrum.id
        : (ENABLE_MAINNET && net === 'MAINNET' ? mainnet.id : selectedChainId));
      if (nextChainId !== selectedChainId) {
        setSelectedChainId(nextChainId);
        loadChainDefaults(nextChainId);
      }
      const list = PRESETS[nextChainId] || [];
      const item = list[presetIdx - 1];
      if (item?.key) setPresetKey(item.key);
      // Force single-position view for deep link
      if (parsed.tokenId) setDeeplinkTokenId(String(parsed.tokenId));
    } catch {}
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  

  // Utility: sleep for backoff
  const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
  type ContractCache = {
    wallets: Record<string, WalletCache>;
    enumerable?: boolean;
  };
  const effectiveWallet = useMemo(() => (manualWallet?.trim() ? manualWallet.trim() : address) || '', [manualWallet, address]);

  const cacheKey = useMemo(() => {
    if (!nftAddress) return '';
    return `nftScan:v1:${selectedChainId}:${nftAddress.toLowerCase()}`;
  }, [nftAddress, selectedChainId]);

  const loadCache = (): ContractCache | null => {
    try {
      if (typeof window === 'undefined' || !cacheKey) return null;
      const raw = localStorage.getItem(cacheKey);
      if (!raw) return null;
      return JSON.parse(raw) as ContractCache;
    } catch {
      return null;
    }
  };
  const saveCache = (update: ContractCache) => {
    try {
      if (typeof window === 'undefined' || !cacheKey) return;
      localStorage.setItem(cacheKey, JSON.stringify(update));
    } catch {}
  };

  // Hydrate detected tokenIds from cache on changes
  useEffect(() => {
    if (!effectiveWallet || !cacheKey) return;
    const cache = loadCache() || { wallets: {} };
    const w = cache.wallets[effectiveWallet.toLowerCase()];
    if (w) {
      const ids = (w.tokenIds || []).map((s) => BigInt(s));
      setDetectedTokenIds(Array.from(new Set(ids)));
      setScanComplete(Boolean(w.complete));
      console.log('[NFT cache] Hydrated from cache', {
        cacheKey,
        wallet: effectiveWallet.toLowerCase(),
        lastScannedIndex: w.lastScannedIndex,
        count: w.tokenIds?.length || 0,
      });
    } else {
      setDetectedTokenIds([]);
      setScanComplete(false);
      console.log('[NFT cache] No wallet entry in cache. Starting fresh', { cacheKey, wallet: effectiveWallet.toLowerCase() });
    }
  }, [effectiveWallet, cacheKey]);

  // OwnerOf scan (batch of 12)
  const scanMore = async () => {
    if (!publicClient || !effectiveWallet || !nftAddress) return;
    // Respect completed scans
    const existing = loadCache();
    const existingEntry = existing?.wallets?.[effectiveWallet.toLowerCase()];
    if (existingEntry?.complete) {
      console.log('[Scan] Already marked complete via gap-limit; skipping.');
      return;
    }
    setScanBusy(true);
    try {
      console.log('[Scan] Starting batch scan…', { nftAddress, wallet: effectiveWallet, chainId: selectedChainId });
      const cache = loadCache() || { wallets: {} };
      const key = effectiveWallet.toLowerCase();
      if (!cache.wallets[key]) {
        cache.wallets[key] = { lastScannedIndex: -1, tokenIds: [], balance: null, updatedAt: Math.floor(Date.now() / 1000), complete: false };
      }
      const w = cache.wallets[key];
      const start = w.lastScannedIndex + 1;
      const end = start + 11;
      const indices = Array.from({ length: 12 }, (_, i) => start + i);
      console.log('[Scan] Indices', { start, end, indices });
      const results: Array<{ i: number; owner: string | null; rateLimited?: boolean }> = [];
      let hadRateLimit = false;
      let consecutiveGaps = 0; // count of consecutive nonexistent token errors
      let gapLimitTriggered = false;
      for (const i of indices) {
        console.log('[Scan] ownerOf call', { tokenId: i });
        let attempt = 0;
        let success = false;
        let owner: string | null = null;
        let wasRateLimitedForThis = false;
        while (attempt < 3 && !success) {
          try {
            owner = await publicClient.readContract({
              abi: erc721Abi,
              address: nftAddress as `0x${string}`,
              functionName: 'ownerOf',
              args: [BigInt(i)],
            }) as string;
            success = true;
            console.log('[Scan] ownerOf result', { tokenId: i, owner });
          } catch (err: any) {
            const msg = String(err?.message || err);
            const code = (err && (err.code || err.status)) as any;
            const is429 = msg.includes('429') || msg.toLowerCase().includes('rate') || code === 429;
            console.warn('[Scan] ownerOf error', { tokenId: i, attempt, is429, err });
            if (is429) {
              hadRateLimit = true;
              wasRateLimitedForThis = true;
              const backoff = 300 * Math.pow(2, attempt); // 300ms, 600ms, 1200ms
              console.log('[Scan] Backing off due to 429/rate limit', { tokenId: i, backoffMs: backoff });
              await sleep(backoff);
            } else {
              // Non rate-limit error, do not retry more than once
              break;
            }
          }
          attempt++;
        }
        // Track gap-limit: count only definitive non-existent (not rate limited)
        if (!success && !wasRateLimitedForThis) {
          consecutiveGaps++;
          console.log('[Scan] Nonexistent token encountered; consecutive gaps =', consecutiveGaps);
          if (consecutiveGaps >= 5) {
            gapLimitTriggered = true;
            console.warn('[Scan] Gap limit reached; stopping scan and marking as complete.');
          }
        } else if (success) {
          consecutiveGaps = 0;
        }
        results.push({ i, owner, rateLimited: !success && wasRateLimitedForThis });
        // Small pacing to avoid bursts
        await sleep(50);
        if (gapLimitTriggered) break;
      }
      const mine = results.filter((r) => (r.owner as string)?.toLowerCase() === effectiveWallet.toLowerCase()).map((r) => r.i.toString());
      console.log('[Scan] Batch done', { foundForMe: mine, total: results.length });
      // Update cache
      const nextIds = Array.from(new Set([...(w.tokenIds || []), ...mine]));
      cache.wallets[key] = {
        ...w,
        lastScannedIndex: hadRateLimit ? w.lastScannedIndex : gapLimitTriggered ? (results.at(-1)?.i ?? w.lastScannedIndex) : end,
        tokenIds: nextIds,
        updatedAt: Math.floor(Date.now() / 1000),
        complete: gapLimitTriggered ? true : w.complete || false,
      };
      saveCache(cache);
      setDetectedTokenIds(nextIds.map((s) => BigInt(s)));
      setScanComplete(Boolean(cache.wallets[key].complete));
      if (hadRateLimit) {
        console.warn('[Scan] Rate limited encountered; did not advance lastScannedIndex. Try again shortly.');
      }
    } finally {
      setScanBusy(false);
    }
  };

  const resetScan = () => {
    if (!effectiveWallet || !cacheKey) return;
    const cache = loadCache() || { wallets: {} };
    const key = effectiveWallet.toLowerCase();
    cache.wallets[key] = { lastScannedIndex: -1, tokenIds: [], balance: null, updatedAt: Math.floor(Date.now() / 1000), complete: false };
    saveCache(cache);
    setDetectedTokenIds([]);
    setScanComplete(false);
    console.log('[Scan] Reset wallet cache', { cacheKey, wallet: effectiveWallet.toLowerCase() });
  };

  const deleteLocalCache = () => {
    if (!cacheKey) return;
    try {
      localStorage.removeItem(cacheKey);
      setDetectedTokenIds([]);
      console.log('[Cache] Deleted contract-scoped cache', { cacheKey });
    } catch (e) {
      console.warn('[Cache] Failed to delete cache', { cacheKey, e });
    }
  };
  const [canEnumerate, setCanEnumerate] = useState<boolean | null>(null);

  // Ensure selected chain for reads
  useEffect(() => {
    if (isConnected && chainId && chainId !== selectedChainId) {
      try {
        switchChain({ chainId: selectedChainId });
      } catch (e) {
        // ignore
      }
    }
  }, [isConnected, chainId, selectedChainId, switchChain]);

  // Helper to load chain defaults into address inputs
  const loadChainDefaults = (id: number) => {
    const d = DEFAULTS[id as keyof typeof DEFAULTS];
    if (d) {
      setNftAddress(d.nft);
      setDebtAddress(d.debt);
      // set first preset for chain
      const p = PRESETS[id]?.[0];
      if (p) setPresetKey(p.key);
      console.log('[UI] Loaded defaults for chain', { selectedChainId: id, nft: d.nft, debt: d.debt });
    }
  };

  // Auto-load defaults when the selected network changes
  useEffect(() => {
    loadChainDefaults(selectedChainId);
  }, [selectedChainId]);

  // Detect ERC721Enumerable support: interfaceId 0x780e9d63
  const { data: supportsEnumerable } = useReadContract({
    abi: erc721Abi,
    address: nftAddress as `0x${string}`,
    functionName: 'supportsInterface',
    args: ['0x780e9d63'],
    chainId: selectedChainId,
    query: { enabled: !!nftAddress },
  });

  useEffect(() => {
    if (supportsEnumerable !== undefined) setCanEnumerate(Boolean(supportsEnumerable));
  }, [supportsEnumerable]);

  // Fetch balance
  const { data: balance } = useReadContract({
    abi: erc721Abi,
    functionName: 'balanceOf',
    args: [effectiveWallet as `0x${string}`],
    chainId: selectedChainId,
    query: { enabled: !!effectiveWallet },
  });

  // Enumerate first up to 10 tokenIds (adjust as needed)
  useEffect(() => {
    const fetchTokens = async () => {
      if (!effectiveWallet || !balance || !canEnumerate) return;
      const count = Number(balance);
      const max = Math.min(count, 10);
      const reads = Array.from({ length: max }, (_, i) => ({
        abi: erc721Abi,
        address: nftAddress as `0x${string}`,
        functionName: 'tokenOfOwnerByIndex' as const,
        args: [effectiveWallet as `0x${string}`, BigInt(i)],
        chainId: selectedChainId,
      }));

      try {
        const res = await (window as any).ethereum; // just to ensure provider exists
        if (!res) return;
      } catch {}

      // We'll use a simple loop via public client later if needed; for now, rely on wagmi's useReadContracts not in hook
    };
    fetchTokens();
  }, [effectiveWallet, balance, canEnumerate, nftAddress, selectedChainId]);

  // Resolve tokenIds: either detected or manual entry
  const tokenIds = useMemo(() => {
    if (deeplinkTokenId.trim()) {
      try { return [BigInt(deeplinkTokenId.trim())]; } catch { return []; }
    }
    const manual = manualTokenId.trim() ? [BigInt(manualTokenId.trim())] : [];
    return [...detectedTokenIds, ...manual];
  }, [detectedTokenIds, manualTokenId, deeplinkTokenId]);

  // Build reads for debt contract
  const debtReads = useMemo(() => {
    if (!tokenIds.length) return [] as any[];
    return tokenIds.flatMap((id) => [
      {
        abi: debtAbi as Abi,
        address: debtAddress as `0x${string}`,
        functionName: 'openDebt',
        args: [id],
        chainId: selectedChainId,
      },
      {
        abi: debtAbi as Abi,
        address: debtAddress as `0x${string}`,
        functionName: 'feeSize',
        args: [id],
        chainId: selectedChainId,
      },
      {
        abi: debtAbi as Abi,
        address: debtAddress as `0x${string}`,
        functionName: 'coinSize',
        args: [id],
        chainId: selectedChainId,
      },
      {
        abi: debtAbi as Abi,
        address: debtAddress as `0x${string}`,
        functionName: 'amountPaid',
        args: [id],
        chainId: selectedChainId,
      },
      {
        abi: debtAbi as Abi,
        address: debtAddress as `0x${string}`,
        functionName: 'startDate',
        args: [id],
        chainId: selectedChainId,
      },
      {
        abi: debtAbi as Abi,
        address: debtAddress as `0x${string}`,
        functionName: 'expiration',
        args: [id],
        chainId: selectedChainId,
      },
      {
        abi: debtAbi as Abi,
        address: debtAddress as `0x${string}`,
        functionName: 'baseSize',
        args: [id],
        chainId: selectedChainId,
      },
    ]);
  }, [tokenIds, debtAddress, selectedChainId]);

  const { data: debtResults, isLoading: debtLoading, error: debtError, refetch } = useReadContracts({
    contracts: debtReads as any,
    query: { enabled: debtReads.length > 0 },
  });

  // Global reads (chain-level): APR and token addresses
  const { data: apr } = useReadContract({
    abi: debtAbi as Abi,
    address: debtAddress as `0x${string}`,
    functionName: 'calculateAPR',
    args: [],
    chainId: selectedChainId,
    query: { enabled: !!debtAddress },
  });
  const { data: stableAddr } = useReadContract({
    abi: debtAbi as Abi,
    address: debtAddress as `0x${string}`,
    functionName: 'stablecoin',
    args: [],
    chainId: selectedChainId,
    query: { enabled: !!debtAddress },
  });
  const { data: contractCoinAddr } = useReadContract({
    abi: debtAbi as Abi,
    address: debtAddress as `0x${string}`,
    functionName: 'contractCoin',
    args: [],
    chainId: selectedChainId,
    query: { enabled: !!debtAddress },
  });

  // Minimal ERC20 ABI for symbol/decimals
  const erc20Abi = useMemo(() => ([
    { type: 'function', name: 'decimals', stateMutability: 'view', inputs: [], outputs: [{ type: 'uint8' }] },
    { type: 'function', name: 'symbol', stateMutability: 'view', inputs: [], outputs: [{ type: 'string' }] },
    { type: 'function', name: 'balanceOf', stateMutability: 'view', inputs: [{ name: 'owner', type: 'address' }], outputs: [{ type: 'uint256' }] },
    { type: 'function', name: 'allowance', stateMutability: 'view', inputs: [{ name: 'owner', type: 'address' }, { name: 'spender', type: 'address' }], outputs: [{ type: 'uint256' }] },
    { type: 'function', name: 'approve', stateMutability: 'nonpayable', inputs: [{ name: 'spender', type: 'address' }, { name: 'value', type: 'uint256' }], outputs: [{ type: 'bool' }] },
  ]) as const satisfies Abi, []);

  const { data: stableDecimals } = useReadContract({
    abi: erc20Abi,
    address: (stableAddr as `0x${string}`) || undefined,
    functionName: 'decimals',
    chainId: selectedChainId,
    query: { enabled: !!stableAddr },
  });
  const { data: stableSymbol } = useReadContract({
    abi: erc20Abi,
    address: (stableAddr as `0x${string}`) || undefined,
    functionName: 'symbol',
    chainId: selectedChainId,
    query: { enabled: !!stableAddr },
  });
  const { data: coinDecimals } = useReadContract({
    abi: erc20Abi,
    address: (contractCoinAddr as `0x${string}`) || undefined,
    functionName: 'decimals',
    chainId: selectedChainId,
    query: { enabled: !!contractCoinAddr },
  });
  const { data: coinSymbol } = useReadContract({
    abi: erc20Abi,
    address: (contractCoinAddr as `0x${string}`) || undefined,
    functionName: 'symbol',
    chainId: selectedChainId,
    query: { enabled: !!contractCoinAddr },
  });

  // Debt token (ERC20 implemented at debtAddress) metadata and balance
  const { data: debtTokenDecimals } = useReadContract({
    abi: erc20Abi,
    address: (debtAddress as `0x${string}`) || undefined,
    functionName: 'decimals',
    chainId: selectedChainId,
    query: { enabled: !!debtAddress },
  });
  const { data: debtTokenSymbol } = useReadContract({
    abi: erc20Abi,
    address: (debtAddress as `0x${string}`) || undefined,
    functionName: 'symbol',
    chainId: selectedChainId,
    query: { enabled: !!debtAddress },
  });
  const { data: debtTokenBalance } = useReadContract({
    abi: erc20Abi,
    address: (debtAddress as `0x${string}`) || undefined,
    functionName: 'balanceOf',
    args: [address as `0x${string}`],
    chainId: selectedChainId,
    query: { enabled: !!debtAddress && !!address },
  });

  // Stablecoin balance and allowance to debt
  const { data: contractCoinBalance } = useReadContract({
    abi: erc20Abi,
    address: (contractCoinAddr as `0x${string}`) || undefined,
    functionName: 'balanceOf',
    args: [address as `0x${string}`],
    chainId: selectedChainId,
    query: { enabled: !!contractCoinAddr && !!address },
  });
  const { data: stableBalance } = useReadContract({
    abi: erc20Abi,
    address: (stableAddr as `0x${string}`) || undefined,
    functionName: 'balanceOf',
    args: [address as `0x${string}`],
    chainId: selectedChainId,
    query: { enabled: !!stableAddr && !!address },
  });
  const { data: stableAllowance } = useReadContract({
    abi: erc20Abi,
    address: (stableAddr as `0x${string}`) || undefined,
    functionName: 'allowance',
    args: [address as `0x${string}`, debtAddress as `0x${string}`],
    chainId: selectedChainId,
    query: { enabled: !!stableAddr && !!address && !!debtAddress },
  });

  // Payment input setter and actions
  const setPaymentAmount = (tokenId: bigint, value: string) => {
    setPayInputs((prev) => ({ ...prev, [tokenId.toString()]: value }));
  };
  const handleApprove = async (tokenId: bigint) => {
    try {
      if (!stableAddr || !debtAddress) return;
      const dec = Number(stableDecimals ?? 6);
      const raw = payInputs[tokenId.toString()] || '0';
      const amount = parseUnits(raw || '0', dec);
      await writeContractAsync({
        abi: erc20Abi,
        address: stableAddr as `0x${string}`,
        functionName: 'approve',
        args: [debtAddress as `0x${string}`, amount],
        chainId: selectedChainId,
      });
      // No direct refetch for allowance; rely on revalidation
    } catch (e) {
      console.warn('[Approve] Failed', e);
    }
  };
  const handlePay = async (tokenId: bigint) => {
    try {
      if (!debtAddress) return;
      const dec = Number(stableDecimals ?? 6);
      const raw = payInputs[tokenId.toString()] || '0';
      let amount = parseUnits(raw || '0', dec);
      const max = payMaxByTokenId[tokenId.toString()];
      if (typeof max === 'bigint' && amount > max) amount = max; // cap to reset-timer amount
      if (amount === BigInt(0)) return;
      await writeContractAsync({
        abi: debtAbi as Abi,
        address: debtAddress as `0x${string}`,
        functionName: 'payDownContract',
        args: [tokenId, amount],
        chainId: selectedChainId,
      });
      // Refresh debt data after payment
      try { await refetch(); } catch {}
    } catch (e) {
      console.warn('[Pay] Failed', e);
    }
  };

  // Helpers to construct provider webhook URL/body for cronhost
  const buildNotificationRequest = (row: { tokenId: bigint; secondsTillLiq?: bigint }, message: string, opts?: { email?: string }) => {
    const provider = notifSettings.provider;
    if (!provider) throw new Error('Notification provider not configured');
    if (provider === 'ntfy') {
      const baseUrl = (notifSettings.ntfyServer || 'https://ntfy.sh').replace(/\/$/, '');
      const topic = notifSettings.ntfyTopic || '';
      const headers: Record<string, string> = { 'Content-Type': 'text/plain' };
      const targetEmail = (opts?.email || notifSettings.email || '').trim();
      if (targetEmail) headers['X-Email'] = String(targetEmail);
      return {
        url: `${baseUrl}/${encodeURIComponent(topic)}`,
        method: 'POST' as const,
        body: message,
        headers,
      };
    }
    if (provider === 'gotify') {
      const baseUrl = (notifSettings.gotifyServer || '').replace(/\/$/, '');
      const token = notifSettings.gotifyToken || '';
      return {
        url: `${baseUrl}/message?token=${encodeURIComponent(token)}`,
        method: 'POST' as const,
        body: { title: 'MortgageFi Alert', message, priority: 5 },
        headers: { 'Content-Type': 'application/json' },
      };
    }
    if (provider === 'sns') {
      // Note: Direct SNS publish requires AWS SigV4; recommend server-side relay. Here we simply target a placeholder you control.
      // User should set up a relay endpoint that publishes to SNS.
      const relay = '/api/sns-relay';
      return {
        url: relay,
        method: 'POST' as const,
        body: { topicArn: notifSettings.snsTopicArn, region: notifSettings.snsRegion, message, email: notifSettings.email },
        headers: { 'Content-Type': 'application/json' },
      };
    }
    throw new Error('Unsupported provider');
  };

  // Single schedule at configured lead time (or 1 day by default)
  const scheduleNotification = async (row: { tokenId: bigint; secondsTillLiq?: bigint }) => {
    const seconds = Number(row.secondsTillLiq ?? 0);
    if (!Number.isFinite(seconds) || seconds <= 0) throw new Error('Invalid liquidation timer');
    const nowSec = Math.floor(Date.now() / 1000);
    const leadSecsCfg = Math.max(0, Math.floor(Number(notifSettings.daysBefore ?? 0) * 86400));
    const offset = leadSecsCfg > 0 ? leadSecsCfg : 86400; // default to 1 day

    // Helper: humanize a duration in seconds
    const human = (rem: number) => {
      if (rem <= 0) return '0m';
      const days = Math.floor(rem / 86400);
      const hours = Math.floor((rem % 86400) / 3600);
      const mins = Math.floor((rem % 3600) / 60);
      if (days >= 2) return `${days}d ${hours}h`;
      if (days === 1) return `1d ${hours}h`;
      if (hours >= 1) return `${hours}h ${mins}m`;
      return `${mins}m`;
    };

    // Amount suggestion: prefer total debt remaining; fallback to 1.5x monthly payment; else current payment pending
    const monthly = (row as any)?.monthlyPaymentSize as bigint | undefined;
    const paymentPending = (row as any)?.currentPaymentPending as bigint | undefined;
    const debtRemaining = (row as any)?.debtAtThisSize as bigint | undefined;
    const origin = (typeof window !== 'undefined' && window.location?.origin) ? window.location.origin : 'https://markets.mortgagefi.app';
    const dappUrl = `${origin}/dapp`;
    const networkSlug = selectedChainId === base.id ? 'BASE' : (selectedChainId === arbitrum.id ? 'ARBITRUM' : (selectedChainId === mainnet.id ? 'MAINNET' : 'UNKNOWN'));
    const presetIdx = Math.max(0, (PRESETS[selectedChainId]?.findIndex(p => p.key === presetKey) ?? 0)) + 1; // 1-based
    const positionUrl = `${origin}/dapp/position/${networkSlug}/${presetIdx}/${row.tokenId.toString()}`;
    const humanLeft = human(offset);

    const mul15 = (v: bigint) => (v * BigInt(3)) / BigInt(2);
    const formatToken = (v: bigint | undefined) => {
      if (v === undefined) return null;
      const dec = Number(stableDecimals ?? 6);
      const factor = BigInt(10) ** BigInt(dec);
      const int = v / factor;
      const frac = (v % factor).toString().padStart(dec, '0').slice(0, 6);
      return `${int.toString()}.${frac}`;
    };

    const payAmt15 = monthly !== undefined ? mul15(monthly) : undefined;
    const payAmtStr = formatToken(debtRemaining ?? payAmt15 ?? paymentPending ?? BigInt(0));
    const payClause = payAmtStr ? `Pay at least ${payAmtStr} USDC to avoid liquidation.` : `Make a payment to avoid liquidation.`;
    // Collateral at risk: use same formatting as UI Loan Collateral
    const collateralStr = fmt((row as any)?.coinSize as bigint | undefined, Number(coinDecimals ?? 8), 8);
    const collateralSym = String(coinSymbol ?? '');

    const msg = `Your position ${row.tokenId.toString()} is approaching liquidation in ~${humanLeft}. ${payClause} Collateral at risk: ${collateralStr ?? 'unknown'} ${collateralSym}. Pay link: ${positionUrl}. Visit ${dappUrl} or https://markets.mortgagefi.app/dashboard.`;

    const liqAt = nowSec + Math.max(0, seconds);
    const runAt1 = liqAt - offset;
    if (runAt1 <= nowSec) throw new Error('Computed run time is in the past');
    // Primary job (to main email)
    const req1 = buildNotificationRequest(row, msg);
    const res1 = await scheduleJob(notifSettings, { runAtEpoch: runAt1, method: req1.method, url: req1.url, body: req1.body, headers: req1.headers });

    // Optional backup job
    const jobs: Array<{ id: string; at: number; label: 'lead' | 'half' | 'last' }> = [
      { id: res1.jobId, at: runAt1, label: 'lead' },
    ];
    const bEmail = (notifSettings.backupEmail || '').trim();
    const bDelayDays = Math.max(0, Number(notifSettings.backupDelayDays ?? 0));
    if (bEmail && bDelayDays >= 1) {
      const requested = runAt1 + Math.floor(bDelayDays * 86400);
      const min1dBeforeLiq = liqAt - 86400; // must be >= 1 day before liq
      const runAt2 = Math.min(requested, min1dBeforeLiq);
      if (runAt2 > nowSec && runAt2 > runAt1) {
        const primaryEmail = (notifSettings.email || '').trim();
        const prefix = `You are the backup contact for this position. Please contact the primary contact${primaryEmail ? ' ' + primaryEmail : ''} immediately. If they do not respond, please pay down the debt to avoid liquidation in a timely manner.`;
        const humanLeftBackup = human(Math.max(0, liqAt - runAt2));
        const msgBackupCore = `Your position ${row.tokenId.toString()} is approaching liquidation in ~${humanLeftBackup}. ${payClause} Collateral at risk: ${collateralStr ?? 'unknown'} ${collateralSym}. Pay link: ${positionUrl}. Visit ${dappUrl} or https://markets.mortgagefi.app/dashboard.`;
        const backupMsg = `${prefix}\n\n${msgBackupCore}`;
        const req2 = buildNotificationRequest(row, backupMsg, { email: bEmail });
        const res2 = await scheduleJob(notifSettings, { runAtEpoch: runAt2, method: req2.method, url: req2.url, body: req2.body, headers: req2.headers });
        jobs.push({ id: res2.jobId, at: runAt2, label: 'last' });
      }
    }

    return { jobs };
  };

  const cancelNotification = async (p: PositionNotification) => {
    // Cancel new multi-jobs first
    if (p.jobs && p.jobs.length) {
      for (const j of p.jobs) {
        try { await cancelScheduledJob(notifSettings, j.id); } catch (e) { console.warn('[scheduler] cancel failed', e); }
      }
    }
    // Back-compat: single jobId
    if (p.jobId) {
      try { await cancelScheduledJob(notifSettings, p.jobId); } catch (e) { console.warn('[scheduler] cancel failed', e); }
    }
  };

  // Scheduled notifications helpers
  const scheduledList = useMemo(() => {
    const entries = Object.entries(positionsNotif || {});
    return entries
      .filter(([, v]) => v && v.enabled && ((v.jobs && v.jobs.length) || v.jobId || v.scheduledAt))
      .map(([k, v]) => {
        const earliest = v.jobs && v.jobs.length ? Math.min(...v.jobs.map(j => j.at)) : (v.scheduledAt || 0);
        return { key: k, ...v, scheduledAt: earliest };
      })
      .sort((a, b) => (a.scheduledAt || 0) - (b.scheduledAt || 0));
  }, [positionsNotif]);

  const deleteSchedule = async (key: string) => {
    const p = positionsNotif[key];
    if (!p) return;
    try { await cancelNotification(p); } catch {}
    setPositionsNotif((prev) => {
      const next = { ...prev };
      delete next[key];
      return next;
    });
  };

  const parsed = useMemo(() => {
    if (!debtResults || !tokenIds.length) return [] as Array<{
      tokenId: bigint;
      currentPaymentPending?: bigint;
      debtAtThisSize?: bigint;
      secondsTillLiq?: bigint;
      feeSize?: bigint;
      coinSize?: bigint;
      amountPaid?: bigint;
      startDate?: bigint;
      expiration?: bigint;
      baseSize?: bigint;
    }>;
    const out: Array<any> = [];
    for (let i = 0; i < tokenIds.length; i++) {
      const baseIdx = i * 7; // openDebt, feeSize, coinSize, amountPaid, startDate, expiration, baseSize
      const openDebtRes = debtResults[baseIdx + 0]?.result as any;
      const feeRes = debtResults[baseIdx + 1]?.result as any;
      const coinSizeRes = debtResults[baseIdx + 2]?.result as any;
      const amountPaidRes = debtResults[baseIdx + 3]?.result as any;
      const startDateRes = debtResults[baseIdx + 4]?.result as any;
      const expirationRes = debtResults[baseIdx + 5]?.result as any;
      const baseSizeRes = debtResults[baseIdx + 6]?.result as any;
      out.push({
        tokenId: tokenIds[i],
        currentPaymentPending: openDebtRes?.[0] as bigint | undefined,
        debtAtThisSize: openDebtRes?.[1] as bigint | undefined,
        secondsTillLiq: openDebtRes?.[2] as bigint | undefined,
        feeSize: feeRes as bigint | undefined,
        coinSize: coinSizeRes as bigint | undefined,
        amountPaid: amountPaidRes as bigint | undefined,
        startDate: startDateRes as bigint | undefined,
        expiration: expirationRes as bigint | undefined,
        baseSize: baseSizeRes as bigint | undefined,
      });
    }
    return out;
  }, [debtResults, tokenIds]);

  // Auto-reschedule when secondsTillLiq changes for enabled positions
  useEffect(() => {
    (async () => {
      for (const row of parsed) {
        const key = row.tokenId.toString();
        const p = positionsNotif[key];
        if (!p || !p.enabled) continue;
        const seconds = Number(row.secondsTillLiq ?? 0);
        if (!Number.isFinite(seconds) || seconds <= 0) continue;
        const lead = Math.max(0, Number(notifSettings.daysBefore ?? 0) * 86400);
        const targetRunAt = Math.floor(Date.now() / 1000) + Math.max(0, seconds - (lead > 0 ? lead : 86400));
        const scheduledAtLead = (p.jobs?.find(j => j.label === 'lead')?.at) || (p.scheduledAt || 0);
        const drift = Math.abs(scheduledAtLead - targetRunAt);
        const hasAnyJob = (p.jobs && p.jobs.length > 0) || Boolean(p.jobId);
        if (!hasAnyJob || drift > 60) {
          // reschedule
          if (p) try { await cancelNotification(p); } catch {}
          try {
            const { jobs } = await scheduleNotification(row);
            setPositionsNotif((prev) => ({ ...prev, [key]: { ...(prev[key] || {}), chainId: selectedChainId, debtAddress, nftAddress, tokenId: key, enabled: true, jobs } }));
          } catch (e) {
            console.warn('[notif] reschedule failed', e);
          }
        }
      }
    })();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [parsed.map(r => String(r.tokenId)+':'+String(r.secondsTillLiq)).join(','), notifSettings.daysBefore, positionsStoreKey]);

  // Map tokenId -> max payable (Amount to reset timer)
  const payMaxByTokenId = useMemo(() => {
    const m: Record<string, bigint> = {};
    for (const row of parsed) {
      if (row.currentPaymentPending !== undefined) m[row.tokenId.toString()] = row.currentPaymentPending as bigint;
    }
    return m;
  }, [parsed]);

  const fmt = (v?: bigint, decimals = 0, precision = 6) => {
    if (v === undefined) return '-';
    const factor = BigInt(10) ** BigInt(decimals);
    const int = v / factor;
    const frac = v % factor;
    const fracStr = (factor + frac).toString().slice(1).padStart(Number(decimals), '0').slice(0, precision);
    return `${int.toString()}${precision > 0 ? '.' + fracStr : ''}`;
  };

  // Initialize default pay amounts to the "Amount to reset timer" when parsed data loads
  useEffect(() => {
    if (!parsed.length) return;
    const dec = Number(stableDecimals ?? 6);
    setPayInputs((prev) => {
      const next = { ...prev };
      for (const row of parsed) {
        const key = row.tokenId.toString();
        if (next[key] === undefined && row.currentPaymentPending !== undefined) {
          next[key] = fmt(row.currentPaymentPending, dec, dec);
        }
      }
      return next;
    });
  }, [parsed, stableDecimals]);

  const aprDisplay = useMemo(() => {
    if (apr === undefined) return '-';
    // Try to infer scale: common is APR in basis points (1e2) or 1e18.
    const n = BigInt(apr as any);
    // Heuristic: if n > 1e9 assume 1e18 scale, else basis points (1e2)
    if (n > BigInt(1000000000)) {
      // percent with 2 decimals: (n / 1e16)
      const percentScaled = n / BigInt(10000000000000000); // 1e16
      return `${fmt(percentScaled, 2, 2)} %`;
    }
    // basis points -> percent with 2 decimals
    return `${fmt(n, 2, 2)} %`;
  }, [apr]);

  const fmtDuration = (seconds?: bigint) => {
    if (seconds === undefined) return '-';
    let s = Number(seconds);
    if (!Number.isFinite(s) || s < 0) return '-';
    const d = Math.floor(s / 86400); s %= 86400;
    const h = Math.floor(s / 3600); s %= 3600;
    const m = Math.floor(s / 60);
    return `${d}d ${h}h ${m}m`;
  };

  // Compound action: raw tx to debt token contract with provided data
  const handleCompound = async () => {
    try {
      if (!debtAddress) return;
      await sendTransactionAsync({
        to: debtAddress as `0x${string}`,
        data: '0x4e71d92d',
        chainId: selectedChainId,
      });
    } catch (e) {
      console.warn('[Compound] Failed', e);
    }
  };

  return (
    <div className="max-w-4xl mx-auto p-6 space-y-6">
      <h1 className="text-2xl font-semibold text-gray-100">MortgageFi DApp</h1>
      <p className="text-sm text-gray-100">Connect wallet, detect your NFTs, and fetch debt details.</p>

      {!isConnected && (
        <div className="rounded border p-4 bg-amber-900/40 border-amber-700 text-amber-200">Please connect your wallet using the Connect Wallet button in the navbar.</div>
      )}

      <div className="grid gap-4 md:grid-cols-2">
        <div className="rounded border p-4 bg-gray-800 border-gray-700 space-y-3">
          <div className="font-medium text-gray-100">Inputs</div>
          <label className="block text-sm text-gray-200">
            <span className="text-gray-300">Network</span>
            <select
              value={selectedChainId}
              onChange={(e) => setSelectedChainId(Number(e.target.value))}
              className="mt-1 w-full border rounded px-2 py-1 bg-gray-900 text-gray-100 border-gray-700"
            >
              <option value={base.id}>Base</option>
              <option value={arbitrum.id}>Arbitrum</option>
            </select>
          </label>

          {/* Preset selector for pair addresses */}
          <label className="block text-sm text-gray-200">
            <span className="text-gray-300">Preset</span>
            <select
              className="mt-1 w-full border rounded px-2 py-1 bg-gray-900 text-gray-100 border-gray-700"
              value={presetKey}
              onChange={(e) => {
                const key = e.target.value;
                setPresetKey(key);
                const list = PRESETS[selectedChainId] || [];
                const found = list.find((x) => x.key === key);
                if (found) {
                  setNftAddress(found.nft);
                  setDebtAddress(found.debt);
                }
              }}
            >
              {(PRESETS[selectedChainId] || []).map((p) => (
                <option key={p.key} value={p.key}>{p.label}</option>
              ))}
            </select>
          </label>
          <label className="block text-sm text-gray-200">
            <span className="text-gray-300">ERC-721 Address</span>
            <input value={nftAddress} onChange={(e) => setNftAddress(e.target.value)} placeholder={DEFAULTS[selectedChainId as keyof typeof DEFAULTS]?.nft} className="mt-1 w-full border rounded px-2 py-1 bg-gray-900 text-gray-100 border-gray-700 placeholder-gray-400" />
          </label>
          <label className="block text-sm text-gray-200">
            <span className="text-gray-300">Debt Contract</span>
            <input value={debtAddress} onChange={(e) => setDebtAddress(e.target.value)} placeholder={DEFAULTS[selectedChainId as keyof typeof DEFAULTS]?.debt} className="mt-1 w-full border rounded px-2 py-1 bg-gray-900 text-gray-100 border-gray-700 placeholder-gray-400" />
          </label>
          <div className="flex items-center gap-2">
            <button
              type="button"
              onClick={() => loadChainDefaults(selectedChainId)}
              className="px-3 py-1.5 bg-gray-700 text-gray-100 rounded hover:bg-gray-600"
            >
              Load defaults for selected network
            </button>
          </div>
          <label className="block text-sm text-gray-200">
            <span className="text-gray-300">Manual Wallet (optional)</span>
            <input value={manualWallet} onChange={(e) => setManualWallet(e.target.value)} placeholder="0xYourWallet" className="mt-1 w-full border rounded px-2 py-1 bg-gray-900 text-gray-100 border-gray-700 placeholder-gray-400" />
            <span className="text-xs text-gray-400">If set, scans and reads will use this address instead of the connected one.</span>
          </label>
          <label className="block text-sm text-gray-200">
            <span className="text-gray-300">Manual Token ID (optional)</span>
            <input value={manualTokenId} onChange={(e) => setManualTokenId(e.target.value)} placeholder="e.g., 103" className="mt-1 w-full border rounded px-2 py-1 bg-gray-900 text-gray-100 border-gray-700 placeholder-gray-400" />
          </label>
          <div className="flex items-center gap-2">
            <button onClick={scanMore} disabled={!effectiveWallet || scanBusy || !nftAddress || scanComplete} className="px-3 py-1.5 bg-indigo-600 text-white rounded disabled:opacity-50">
              {scanBusy ? 'Scanning…' : scanComplete ? 'Scan Complete' : 'Scan 12 more (ownerOf)'}
            </button>
            <button onClick={resetScan} disabled={!effectiveWallet || scanBusy || !nftAddress} className="px-3 py-1.5 bg-gray-700 text-gray-100 rounded hover:bg-gray-600">
              Reset
            </button>
            <button onClick={deleteLocalCache} disabled={scanBusy || !nftAddress} className="px-3 py-1.5 bg-gray-700 text-gray-100 rounded hover:bg-gray-600">
              Delete Local Cache
            </button>
          </div>
          {scanComplete && (
            <div className="text-xs text-green-200 bg-green-900/30 border border-green-700 rounded p-2 mt-2">
              Owner scan completed (gap limit reached). Reset or delete cache to rescan.
            </div>
          )}
          <div className="text-xs text-gray-300">
            Cached IDs: {detectedTokenIds.length ? detectedTokenIds.map((id) => id.toString()).join(', ') : '—'}
          </div>
        </div>
        <div className="rounded border p-4 bg-gray-800 border-gray-700">
          <div className="font-medium text-gray-100">About</div>
          <p className="text-sm text-gray-300 mt-1">Enter your NFT contract and token ID(s), then fetch debt details from the mortgage contract.</p>
          <p className="text-xs text-gray-400 mt-2">{selectedChainId === base.id ? 'Base' : selectedChainId === arbitrum.id ? 'Arbitrum' : 'Selected'} network is required. The app will attempt to detect your token IDs via on-chain queries when supported.</p>
        </div>

        <div className="rounded border p-4 bg-gray-800 border-gray-700 space-y-3">
          <div className="flex items-center justify-between">
            <div className="font-medium text-gray-100">Scheduled notifications</div>
            <button
              className="text-xs px-2 py-1 border border-red-700 text-red-300 hover:bg-red-900/40 rounded"
              onClick={async () => {
                // Purge remotely from Schedy by ntfy topic URL
                try { await purgeNtfyTopicSchedules(notifSettings); } catch (e) { console.warn('[schedy] purge failed', e); }
                // Also clear any local state we track
                if (scheduledList.length) {
                  for (const it of scheduledList) {
                    try { await deleteSchedule(it.key); } catch {}
                  }
                }
              }}
            >
              Delete all
            </button>
          </div>
          {scheduledList.length === 0 ? (
            <div className="text-sm text-gray-400">No schedules yet.</div>
          ) : (
            <ul className="divide-y divide-gray-700">
              {scheduledList.map((it) => (
                <li key={it.key} className="py-2 flex items-center justify-between text-sm">
                  <div className="space-y-0.5">
                    <div className="text-gray-200">Token #{it.tokenId}</div>
                    <div className="text-gray-400">
                      {it.scheduledAt ? `Runs at ${new Date(it.scheduledAt * 1000).toLocaleString()}` : 'Scheduled'}
                      {it.jobId ? ` · Job ${it.jobId}` : ''}
                    </div>
                  </div>
                  <button
                    className="px-2 py-1 border border-gray-600 hover:bg-gray-700 rounded text-gray-200"
                    onClick={() => deleteSchedule(it.key)}
                  >
                    Delete
                  </button>
                </li>
              ))}
            </ul>
          )}
        </div>
      </div>

      <div className="flex flex-wrap items-center gap-3 text-gray-100">
        <button onClick={() => refetch()} disabled={!tokenIds.length} className="px-4 py-2 bg-indigo-600 text-white rounded disabled:opacity-50">Fetch Debt Data</button>
        <span className="text-sm">Chain: {selectedChainId === base.id ? 'Base' : selectedChainId === arbitrum.id ? 'Arbitrum' : 'Other'}</span>
        <span className="text-sm">{String(coinSymbol ?? '')} Balance: {fmt(contractCoinBalance as bigint | undefined, Number(coinDecimals ?? 8), 8)} {String(coinSymbol ?? '')}</span>
        <span className="text-sm">Stable Balance: {fmt(stableBalance as bigint | undefined, Number(stableDecimals ?? 6), 6)} {String(stableSymbol ?? '')}</span>
        <span className="text-sm">Allowance → Debt: {fmt(stableAllowance as bigint | undefined, Number(stableDecimals ?? 6), 6)} {String(stableSymbol ?? '')}</span>
        <span className="text-sm flex items-center gap-2">
          Debt Token Balance: {fmt(debtTokenBalance as bigint | undefined, Number(debtTokenDecimals ?? 18), 6)} {String(debtTokenSymbol ?? '')}
          {(() => {
            const dec = Number(debtTokenDecimals ?? 18);
            const bal = (debtTokenBalance as bigint | undefined) ?? undefined;
            const oneToken = BigInt(10) ** BigInt(dec);
            const hasOnePlus = bal !== undefined && bal > oneToken;
            const manualSet = Boolean(manualWallet?.trim());
            const canCompound = !!address && !manualSet && !!debtAddress && hasOnePlus && !txPending;
            return (
              <button
                className="px-2 py-1 bg-emerald-600 text-white rounded disabled:opacity-50 text-xs"
                disabled={!canCompound}
                onClick={handleCompound}
                title={manualSet ? 'Compound disabled when Manual Wallet is set' : (!hasOnePlus ? 'Requires > 1 token balance' : undefined)}
              >
                {txPending ? 'Compounding…' : 'Compound'}
              </button>
            );
          })()}
        </span>
      </div>

      {debtError && <div className="rounded border border-red-300 bg-red-50 p-3 text-sm text-red-800">{String(debtError?.message || debtError)}</div>}

      <div className="space-y-3">
        {debtLoading && <div>Loading...</div>}
        {!debtLoading && parsed.map((row) => {
          const stableDec = Number(stableDecimals ?? 6);
          const coinDec = Number(coinDecimals ?? 8);
          // Interpret expiration: if small (<= 200), treat as years; otherwise treat as unix seconds
          let loanTermDisplay = '-';
          if (row.expiration !== undefined) {
            const exp = Number(row.expiration);
            if (!Number.isNaN(exp)) {
              if (exp <= 200) {
                loanTermDisplay = `${exp} Years`;
              } else {
                loanTermDisplay = new Date(exp * 1000).toLocaleDateString();
              }
            }
          }
          // Precompute values
          const costToClose2pct: bigint | undefined =
            row.baseSize !== undefined ? (row.baseSize as bigint) * BigInt(2) / BigInt(100) : undefined;
          const totalCostToClose102pct: bigint | undefined =
            row.baseSize !== undefined ? (row.baseSize as bigint) * BigInt(102) / BigInt(100) : undefined;
          const monthlyPaymentFixed: bigint | undefined =
            row.baseSize !== undefined && row.feeSize !== undefined
              ? ((row.baseSize as bigint) + (row.feeSize as bigint)) / BigInt(360)
              : undefined;
          const isClosed = row.coinSize !== undefined && row.coinSize === BigInt(0);
          return (
            <div key={row.tokenId.toString()} className={`rounded border p-4 shadow-sm ${isClosed ? 'bg-gray-50 border-gray-200' : 'bg-white'}`}>
              <div className="flex items-center gap-2">
                <div className="font-semibold text-gray-900">Loan Details for {String(stableSymbol ?? 'USDC')}-{String(coinSymbol ?? 'cbBTC')} - ID: {row.tokenId.toString()}</div>
                {isClosed && (
                  <span className="inline-flex items-center rounded px-2 py-0.5 text-xs font-medium bg-yellow-100 text-yellow-800 border border-yellow-200">
                    Closed/Defaulted
                  </span>
                )}
              </div>
              <div className="grid grid-cols-2 gap-2 text-sm mt-2">
                <div className="text-gray-800">Loan Collateral</div>
                <div className={`text-gray-900 ${isClosed ? 'line-through' : ''}`}>{fmt(row.coinSize, coinDec, 8)} {String(coinSymbol ?? '')}</div>

                <div className="text-gray-800">Interest Rate</div>
                <div className="text-gray-900">{aprDisplay}</div>

                <div className="text-gray-800">Loan Dollar Value</div>
                <div className="text-gray-900">{fmt(row.baseSize, stableDec, 6)} {String(stableSymbol ?? '')}</div>

                <div className="text-gray-800">Cost to close Loan</div>
                <div className="text-gray-900">{fmt(costToClose2pct, stableDec, 6)} {String(stableSymbol ?? '')}</div>

                <div className="text-gray-800">Total Cost to Close</div>
                <div className="text-gray-900">{fmt(totalCostToClose102pct, stableDec, 6)} {String(stableSymbol ?? '')}</div>

                <div className="text-gray-800">Total Interest to pay over Length of Loan</div>
                <div className="text-gray-900">{fmt(row.feeSize, stableDec, 6)} {String(stableSymbol ?? '')}</div>

                <div className="text-gray-800">Total Repaid</div>
                <div className="text-gray-900">{fmt(row.amountPaid, stableDec, 6)} {String(stableSymbol ?? '')}</div>

                <div className="text-gray-800">Debt Remaining</div>
                <div className="text-gray-900">{fmt(row.debtAtThisSize, stableDec, 6)} {String(stableSymbol ?? '')}</div>

                <div className="text-gray-800">Amount to reset timer</div>
                <div className="text-gray-900">{fmt(row.currentPaymentPending, stableDec, 6)} {String(stableSymbol ?? '')}</div>

                <div className="text-gray-800">Monthly Payment Size</div>
                <div className="text-gray-900">{fmt(monthlyPaymentFixed, stableDec, 6)} {String(stableSymbol ?? '')}</div>

                <div className="text-gray-800">Loan Term</div>
                <div className="text-gray-900">{loanTermDisplay}</div>

                <div className="text-gray-800">Start Date</div>
                <div className="text-gray-900">{row.startDate ? new Date(Number(row.startDate) * 1000).toLocaleDateString() : '-'}</div>

                <div className="text-gray-800">Seconds Till Liquidation</div>
                <div className="text-gray-900 flex items-center gap-3">
                  <span>{fmtDuration(row.secondsTillLiq)}</span>
                  {!isClosed && (
                    (() => {
                      const key = row.tokenId.toString();
                      const p = positionsNotif[key];
                      const checked = Boolean(p?.enabled);
                      const disabled = row.secondsTillLiq === undefined || Number(row.secondsTillLiq) <= 0;
                      const onToggle = async (next: boolean) => {
                        // Ensure settings
                        const haveProvider = (() => {
                          if (!notifSettings.provider) return false;
                          if (notifSettings.provider === 'ntfy') return Boolean(notifSettings.ntfyServer && notifSettings.ntfyTopic);
                          if (notifSettings.provider === 'gotify') return Boolean(notifSettings.gotifyServer && notifSettings.gotifyToken);
                          if (notifSettings.provider === 'sns') return Boolean(notifSettings.snsRegion && notifSettings.snsTopicArn && notifSettings.snsAccessKeyId && notifSettings.snsSecretAccessKey);
                          return false;
                        })();
                        const haveScheduler = (() => {
                          const s = notifSettings.scheduler || 'schedy';
                          if (s !== 'schedy') return false;
                          return Boolean(notifSettings.schedyBaseUrl && notifSettings.schedyApiKey);
                        })();
                        const haveSettings = Boolean(haveProvider && haveScheduler && notifSettings.email);
                        if (next && !haveSettings) { setSettingsOpen(true); return; }
                        if (!next) {
                          // disable and cancel
                          if (p) await cancelNotification(p);
                          setPositionsNotif((prev) => ({ ...prev, [key]: { chainId: selectedChainId, debtAddress, nftAddress, tokenId: key, enabled: false } }));
                          return;
                        }
                        try {
                          const { jobs } = await scheduleNotification(row);
                          setPositionsNotif((prev) => ({
                            ...prev,
                            [key]: { chainId: selectedChainId, debtAddress, nftAddress, tokenId: key, enabled: true, jobs },
                          }));
                        } catch (e) {
                          console.warn('[notif] schedule failed', e);
                        }
                      };
                      return (
                        <label className="inline-flex items-center gap-1 text-xs text-gray-700">
                          <input type="checkbox" className="h-4 w-4" disabled={disabled} checked={checked} onChange={(e) => onToggle(e.target.checked)} />
                          Enable alert
                        </label>
                      );
                    })()
                  )}
                </div>
              </div>

              {/* Pay controls (hidden for Closed/Defaulted loans) */}
              {!isClosed && (
                <div className="mt-3 border-t pt-3">
                  <div className="text-sm font-medium text-gray-900 mb-2">Make a Payment</div>
                  <div className="flex flex-wrap items-center gap-2">
                    {(() => {
                      const key = row.tokenId.toString();
                      const inputVal = payInputs[key] ?? '';
                      const dec = Number(stableDecimals ?? 6);
                      let amount: bigint | null = null;
                      try { amount = inputVal ? parseUnits(inputVal, dec) : null; } catch { amount = null; }
                      const hasAllowance = stableAllowance !== undefined && amount !== null && (stableAllowance as bigint) >= amount;
                      const hasBalance = stableBalance !== undefined && amount !== null && (stableBalance as bigint) >= amount;
                      const disableAll = !stableAddr || !debtAddress;
                      return (
                        <>
                          <input
                            type="text"
                            inputMode="decimal"
                            placeholder={`Amount in ${String(stableSymbol ?? '')}`}
                            className="border rounded px-2 py-1 text-sm w-40 text-gray-900 placeholder-gray-500"
                            value={inputVal}
                            onChange={(e) => setPaymentAmount(row.tokenId, e.target.value)}
                          />
                          {!hasAllowance ? (
                            <button
                              className="px-3 py-1.5 bg-gray-200 text-gray-900 rounded"
                              onClick={() => handleApprove(row.tokenId)}
                              disabled={disableAll || amount === null || amount === BigInt(0)}
                            >
                              Approve
                            </button>
                          ) : null}
                          <button
                            className="px-3 py-1.5 bg-indigo-600 text-white rounded disabled:opacity-50"
                            onClick={() => handlePay(row.tokenId)}
                            disabled={disableAll || amount === null || amount === BigInt(0) || !hasAllowance || !hasBalance}
                            title={!hasAllowance ? 'Approve the stablecoin first' : (!hasBalance ? 'Insufficient balance' : undefined)}
                          >
                            Pay
                          </button>
                          {!hasBalance && amount !== null && (
                            <span className="text-xs text-red-600">Insufficient balance</span>
                          )}
                        </>
                      );
                    })()}
                  </div>
                </div>
              )}
            </div>
          );
        })}
      </div>

      {/* Footer note removed per requirements */}

      {/* Settings Modal */}
      <SettingsModal
        open={settingsOpen}
        initial={notifSettings as NotificationSettings}
        onClose={() => setSettingsOpen(false)}
        onSave={(s) => {
          console.log('[Settings] Save requested', s);
          setNotifSettings(s);
          setSettingsOpen(false);
        }}
      />
    </div>
  );
}
