/**
 * ERC-5267 — read eip712Domain() via eth_call and decode the standard return tuple:
 * (bytes1 fields, string name, string version, uint256 chainId, address verifyingContract, bytes32 salt, uint256[] extensions)
 */

export interface DecodedEip712Domain {
  fields: string
  name: string
  version: string
  chainId: string
  verifyingContract: string
  salt: string
  extensionsSummary: string
}

const EIP712_DOMAIN_SELECTOR = '84b0196e'

function hexToBytes(hex: string): Uint8Array {
  const normalized = hex.replace(/^0x/i, '')
  if (normalized.length % 2 !== 0) return new Uint8Array()
  const out = new Uint8Array(normalized.length / 2)
  for (let i = 0; i < out.length; i++) {
    out[i] = parseInt(normalized.slice(i * 2, i * 2 + 2), 16)
  }
  return out
}

function readWord(bytes: Uint8Array, wordIndex: number): Uint8Array {
  const start = wordIndex * 32
  return bytes.subarray(start, start + 32)
}

function wordToBigInt(word: Uint8Array): bigint {
  let v = 0n
  for (let i = 0; i < word.length; i++) {
    v = (v << 8n) + BigInt(word[i] || 0)
  }
  return v
}

function readBytes1(word: Uint8Array): string {
  return `0x${(word[31] ?? 0).toString(16).padStart(2, '0')}`
}

function readAddress(word: Uint8Array): string {
  const slice = word.subarray(12, 32)
  return `0x${Array.from(slice)
    .map((b) => b.toString(16).padStart(2, '0'))
    .join('')}`
}

function readBytes32(word: Uint8Array): string {
  return `0x${Array.from(word)
    .map((b) => b.toString(16).padStart(2, '0'))
    .join('')}`
}

function readAbiString(bytes: Uint8Array, offset: number): { value: string; next: number } {
  if (offset + 32 > bytes.length) return { value: '', next: offset }
  const len = Number(wordToBigInt(bytes.subarray(offset, offset + 32)))
  const start = offset + 32
  const end = start + len
  if (end > bytes.length) return { value: '', next: offset }
  const raw = bytes.subarray(start, end)
  const value = new TextDecoder().decode(raw).replace(/\u0000+$/g, '')
  const padded = Math.ceil(len / 32) * 32
  return { value, next: start + padded }
}

function readUint256Array(bytes: Uint8Array, offset: number): { summary: string; next: number } {
  if (offset + 32 > bytes.length) return { summary: '[]', next: offset }
  const n = Number(wordToBigInt(bytes.subarray(offset, offset + 32)))
  if (!Number.isFinite(n) || n < 0 || n > 256) {
    return { summary: `(length ${n})`, next: offset + 32 }
  }
  let pos = offset + 32
  const parts: string[] = []
  for (let i = 0; i < n; i++) {
    if (pos + 32 > bytes.length) break
    parts.push(wordToBigInt(bytes.subarray(pos, pos + 32)).toString())
    pos += 32
  }
  const summary = n === 0 ? '[]' : `[${parts.join(', ')}]`
  return { summary, next: pos }
}

export function decodeEip712DomainReturnData(resultHex: string): DecodedEip712Domain | null {
  const bytes = hexToBytes(resultHex)
  if (bytes.length < 32 * 7) return null

  const fields = readBytes1(readWord(bytes, 0))
  const nameOff = Number(wordToBigInt(readWord(bytes, 1)))
  const versionOff = Number(wordToBigInt(readWord(bytes, 2)))
  const chainId = wordToBigInt(readWord(bytes, 3)).toString()
  const verifyingContract = readAddress(readWord(bytes, 4))
  const salt = readBytes32(readWord(bytes, 5))
  const extOff = Number(wordToBigInt(readWord(bytes, 6)))

  if (!Number.isFinite(nameOff) || nameOff < 0 || nameOff >= bytes.length) return null
  if (!Number.isFinite(versionOff) || versionOff < 0 || versionOff >= bytes.length) return null

  const name = readAbiString(bytes, nameOff).value
  const version = readAbiString(bytes, versionOff).value
  const extensionsSummary =
    Number.isFinite(extOff) && extOff >= 0 && extOff < bytes.length ? readUint256Array(bytes, extOff).summary : '[]'

  return {
    fields,
    name,
    version,
    chainId,
    verifyingContract,
    salt,
    extensionsSummary,
  }
}

function getPublicRpcUrl(): string {
  if (typeof window !== 'undefined' && process.env.NEXT_PUBLIC_RPC_URL_138) {
    return process.env.NEXT_PUBLIC_RPC_URL_138
  }
  return process.env.NEXT_PUBLIC_RPC_URL_138 || 'https://rpc-http-pub.d-bis.org'
}

export async function fetchEip712DomainDecoded(address: string): Promise<DecodedEip712Domain | null> {
  const trimmed = address.trim()
  if (!/^0x[a-fA-F0-9]{40}$/.test(trimmed)) return null

  const data = `0x${EIP712_DOMAIN_SELECTOR}`
  try {
    const response = await fetch(getPublicRpcUrl(), {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        jsonrpc: '2.0',
        id: 1,
        method: 'eth_call',
        params: [{ to: trimmed, data }, 'latest'],
      }),
    })
    if (!response.ok) return null
    const payload = (await response.json()) as { result?: string; error?: { message?: string } }
    if (payload.error?.message || !payload.result || payload.result === '0x') return null
    return decodeEip712DomainReturnData(payload.result)
  } catch {
    return null
  }
}
