import { formatAmount, formatRawAmount, parseAmount } from '../shared/amount';
import { createFlagWrap, getFlagBaseUrl } from '../shared/flags';
import { resolveLocaleForElement } from '../shared/locale';
import { addRecentCurrency, getRecentCurrencies } from '../shared/recent-currencies';
import type { CurrencyInfo, RateData } from '../shared/types';
import { parseRateDataScript } from './rate-data';
import { convertAmount, getPairPricing } from './rates';

let _flagBaseUrl: string | undefined;
function flagBaseUrl(): string {
  if (_flagBaseUrl === undefined) {
    _flagBaseUrl = getFlagBaseUrl();
  }
  return _flagBaseUrl;
}
const CHECK_ICON_PATH = 'M9.9997 15.1709L19.1921 5.97852L20.6063 7.39273L9.9997 17.9993L3.63574 11.6354L5.04996 10.2212L9.9997 15.1709Z';

class FxConverter {
  private el: HTMLElement;
  private data: RateData;
  private decimals: number;
  private locale: string;
  private fromCode: string;
  private toCode: string;
  private fromInput: HTMLInputElement;
  private toInput: HTMLInputElement;
  private fromBtn: HTMLButtonElement;
  private toBtn: HTMLButtonElement;
  private swapBtn: HTMLButtonElement;
  private fromLabelEl: HTMLElement | null;
  private toLabelEl: HTMLElement | null;
  private ctaEl: HTMLAnchorElement | null;
  private srStatusEl: HTMLElement | null;
  private srAnnounceTimer = 0;
  private swapRotated = false;
  private activeDropdownTarget: 'from' | 'to' | null = null;
  private dropdownEl: HTMLElement | null = null;
  private dropdownRAF = 0;
  private isEditingInput = false;
  private activeInput: 'from' | 'to' | null = null;
  private onScrollOrResize = (): void => this.repositionDropdown();

  private getUiConfig() {
    return window.crtodayConverterUi ?? {};
  }

  private getUiLabels() {
    return this.getUiConfig().labels ?? {};
  }

  private getGroupLabels(): Record<string, string> {
    return this.getUiConfig().groupLabels ?? {};
  }

  private getGroupOrder(): string[] {
    return this.getUiConfig().groupOrder ?? [];
  }

  private formatAmountAriaLabel(code: string): string {
    const pattern = this.getUiLabels().amountInPattern ?? '';
    return pattern.replace('%s', code);
  }

  constructor(el: HTMLElement) {
    this.el = el;

    const script = el.querySelector<HTMLScriptElement>('script.crtoday-conv-data');
    this.data = parseRateDataScript(script);

    this.decimals = parseInt(el.dataset.crtodayDecimals || '2', 10);
    this.locale = resolveLocaleForElement(this.el);
    this.fromCode = el.dataset.crtodayFrom || 'USD';
    this.toCode = el.dataset.crtodayTo || 'EUR';

    this.fromInput = el.querySelector('.crtoday-conv-from .crtoday-conv-input') as HTMLInputElement;
    this.toInput = el.querySelector('.crtoday-conv-to .crtoday-conv-input') as HTMLInputElement;
    this.fromBtn = el.querySelector('.crtoday-conv-from .crtoday-conv-currency') as HTMLButtonElement;
    this.toBtn = el.querySelector('.crtoday-conv-to .crtoday-conv-currency') as HTMLButtonElement;
    this.swapBtn = el.querySelector('.crtoday-conv-swap') as HTMLButtonElement;

    if (!this.fromInput || !this.toInput || !this.fromBtn || !this.toBtn || !this.swapBtn) {
      throw new Error('CRToday: converter DOM structure incomplete');
    }

    this.fromLabelEl = el.querySelector('.crtoday-conv-from .crtoday-conv-label');
    this.toLabelEl = el.querySelector('.crtoday-conv-to .crtoday-conv-label');
    this.ctaEl = el.querySelector('.crtoday-conv-cta');
    this.srStatusEl = el.querySelector('.crtoday-conv-sr-status');

    this.bindEvents();
    this.updatePricingPresentation();
    this.applyFormattedValues();
    this.updateCtaHref();
  }

  private bindEvents(): void {
    this.fromInput.addEventListener('input', () => this.onFromInput());
    this.toInput.addEventListener('input', () => this.onToInput());
    this.fromInput.addEventListener('focus', () => this.onInputFocus('from'));
    this.toInput.addEventListener('focus', () => this.onInputFocus('to'));
    this.fromInput.addEventListener('blur', () => this.onInputBlur());
    this.toInput.addEventListener('blur', () => this.onInputBlur());

    this.swapBtn.addEventListener('click', () => this.swap());

    this.fromBtn.addEventListener('click', (e) => {
      e.stopPropagation();
      this.openDropdown('from');
    });
    this.toBtn.addEventListener('click', (e) => {
      e.stopPropagation();
      this.openDropdown('to');
    });

    document.addEventListener('click', (e) => {
      if (this.dropdownEl && !this.dropdownEl.contains(e.target as Node)) {
        this.closeDropdown();
      }
    });
  }

  private onInputFocus(target: 'from' | 'to'): void {
    this.isEditingInput = true;
    this.activeInput = target;

    this.applyRawValues();
  }

  private onInputBlur(): void {
    requestAnimationFrame(() => {
      const active = document.activeElement;
      if (active === this.fromInput || active === this.toInput) {
        return;
      }

      const source = this.activeInput ?? 'from';
      this.isEditingInput = false;
      this.activeInput = null;

      if (source === 'to') {
        this.onToInput(false);
      } else {
        this.onFromInput(false);
      }

      this.applyFormattedValues();
      this.updateCtaHref();
    });
  }

  private applyRawValues(): void {
    const fromAmount = parseAmount(this.fromInput.value, this.locale);
    const toAmount = parseAmount(this.toInput.value, this.locale);

    this.fromInput.value = fromAmount !== null ? formatRawAmount(fromAmount, this.decimals) : '';
    this.toInput.value = toAmount !== null ? formatRawAmount(toAmount, this.decimals) : '';
  }

  private applyFormattedValues(): void {
    const fromAmount = parseAmount(this.fromInput.value, this.locale);
    const toAmount = parseAmount(this.toInput.value, this.locale);

    this.fromInput.value = fromAmount !== null
      ? formatAmount(fromAmount, this.decimals, this.locale)
      : '';
    this.toInput.value = toAmount !== null
      ? formatAmount(toAmount, this.decimals, this.locale)
      : '';
  }

  private announceResult(resultCode: string, resultValue: string): void {
    if (!this.srStatusEl) return;
    window.clearTimeout(this.srAnnounceTimer);
    this.srAnnounceTimer = window.setTimeout(() => {
      if (!this.srStatusEl) return;
      this.srStatusEl.textContent = resultValue
        ? `${resultValue} ${resultCode}`
        : '';
    }, 800);
  }

  private onFromInput(forceRawOutput: boolean = this.isEditingInput): void {
    const amount = parseAmount(this.fromInput.value, this.locale);
    if (amount === null) {
      this.toInput.value = '';
      this.announceResult(this.toCode, '');
      this.updateCtaHref();
      return;
    }

    const result = convertAmount(amount, this.fromCode, this.toCode, this.data);
    this.toInput.value = result !== null
      ? (forceRawOutput
        ? formatRawAmount(result, this.decimals)
        : formatAmount(result, this.decimals, this.locale))
      : '';
    this.announceResult(this.toCode, this.toInput.value);
    this.updateCtaHref();
  }

  private onToInput(forceRawOutput: boolean = this.isEditingInput): void {
    const amount = parseAmount(this.toInput.value, this.locale);
    if (amount === null) {
      this.fromInput.value = '';
      this.announceResult(this.fromCode, '');
      this.updateCtaHref();
      return;
    }

    const result = convertAmount(amount, this.toCode, this.fromCode, this.data);
    this.fromInput.value = result !== null
      ? (forceRawOutput
        ? formatRawAmount(result, this.decimals)
        : formatAmount(result, this.decimals, this.locale))
      : '';
    this.announceResult(this.fromCode, this.fromInput.value);
    this.updateCtaHref();
  }

  private swap(): void {
    const tmp = this.fromCode;
    this.fromCode = this.toCode;
    this.toCode = tmp;

    this.el.dataset.crtodayFrom = this.fromCode;
    this.el.dataset.crtodayTo = this.toCode;

    this.updateCurrencyButton(this.fromBtn, this.fromCode);
    this.updateCurrencyButton(this.toBtn, this.toCode);

    this.fromInput.setAttribute('aria-label', this.formatAmountAriaLabel(this.fromCode));
    this.toInput.setAttribute('aria-label', this.formatAmountAriaLabel(this.toCode));

    this.swapRotated = !this.swapRotated;
    const svg = this.swapBtn.querySelector('svg');
    if (svg) {
      svg.style.transition = 'transform 0.3s ease';
      svg.style.transform = this.swapRotated ? 'rotate(180deg)' : 'rotate(0deg)';
    }

    this.updatePricingPresentation();
    this.onFromInput();
  }

  private updatePricingPresentation(): void {
    const pricing = getPairPricing(this.fromCode, this.toCode, this.data);
    this.el.dataset.crtodayPricing = pricing;
    this.el.classList.remove('crtoday-pricing-mid', 'crtoday-pricing-bidask');
    this.el.classList.add(`crtoday-pricing-${pricing}`);

    const labels = this.data.labels ?? {};
    const fromText = pricing === 'bidask'
      ? (labels.bidask?.from || '')
      : (labels.mid?.from || '');
    const toText = pricing === 'bidask'
      ? (labels.bidask?.to || '')
      : (labels.mid?.to || '');

    if (this.fromLabelEl) this.fromLabelEl.textContent = fromText;
    if (this.toLabelEl) this.toLabelEl.textContent = toText;
  }

  private updateCtaHref(): void {
    if (!this.ctaEl) return;

    const template = this.ctaEl.dataset.crtodayCtaTemplate || '';
    const amountValue = parseAmount(this.fromInput.value, this.locale);
    const resultValue = parseAmount(this.toInput.value, this.locale);

    const amountRaw = amountValue !== null ? formatRawAmount(amountValue, this.decimals) : '';
    const resultRaw = resultValue !== null ? formatRawAmount(resultValue, this.decimals) : '';

    if (!template || amountRaw === '' || resultRaw === '') {
      this.setCtaDisabled();
      return;
    }

    const vars: Record<string, string> = {
      amount: amountRaw,
      from: this.fromCode,
      to: this.toCode,
      result: resultRaw,
    };

    const href = template.replace(/\{(amount|from|to|result)\}/g, (_match, key: string) => {
      const value = vars[key] ?? '';
      return encodeURIComponent(value);
    });

    if (!href) {
      this.setCtaDisabled();
      return;
    }

    this.setCtaEnabled(href);
  }

  private setCtaDisabled(): void {
    if (!this.ctaEl) {
      return;
    }

    this.ctaEl.classList.add('crtoday-conv-cta-disabled');
    this.ctaEl.setAttribute('aria-disabled', 'true');
    this.ctaEl.setAttribute('href', '#');
  }

  private setCtaEnabled(href: string): void {
    if (!this.ctaEl) {
      return;
    }

    this.ctaEl.classList.remove('crtoday-conv-cta-disabled');
    this.ctaEl.removeAttribute('aria-disabled');
    this.ctaEl.setAttribute('href', href);
  }

  private openDropdown(target: 'from' | 'to'): void {
    if (this.activeDropdownTarget === target && this.dropdownEl) {
      this.closeDropdown();
      return;
    }

    this.closeDropdown();
    this.activeDropdownTarget = target;

    const btn = target === 'from' ? this.fromBtn : this.toBtn;
    btn.setAttribute('aria-expanded', 'true');

    const dropdown = this.buildDropdown(target);
    this.dropdownEl = dropdown;

    this.el.appendChild(dropdown);
    this.positionDropdown(btn);

    window.addEventListener('scroll', this.onScrollOrResize, { passive: true, capture: true });
    window.addEventListener('resize', this.onScrollOrResize, { passive: true });

    const search = dropdown.querySelector<HTMLInputElement>('.crtoday-conv-search');
    if (search) {
      requestAnimationFrame(() => search.focus());
    }
  }

  private closeDropdown(): void {
    cancelAnimationFrame(this.dropdownRAF);
    window.removeEventListener('scroll', this.onScrollOrResize, true);
    window.removeEventListener('resize', this.onScrollOrResize);

    if (this.dropdownEl) {
      this.dropdownEl.remove();
      this.dropdownEl = null;
    }

    if (this.activeDropdownTarget) {
      const btn = this.activeDropdownTarget === 'from' ? this.fromBtn : this.toBtn;
      btn.setAttribute('aria-expanded', 'false');
      this.activeDropdownTarget = null;
    }
  }

  private positionDropdown(btn: HTMLButtonElement): void {
    const dd = this.dropdownEl;
    if (!dd) return;

    const field = btn.closest('.crtoday-conv-field') as HTMLElement;
    const rect = field.getBoundingClientRect();
    const gap = 6;

    dd.style.left = `${rect.left}px`;
    dd.style.width = `${rect.width}px`;

    const spaceBelow = window.innerHeight - rect.bottom - gap;
    const ddHeight = dd.offsetHeight || 340;

    if (spaceBelow >= ddHeight || spaceBelow >= rect.top - gap) {
      dd.style.top = `${rect.bottom + gap}px`;
      dd.style.bottom = 'auto';
    } else {
      dd.style.top = 'auto';
      dd.style.bottom = `${window.innerHeight - rect.top + gap}px`;
    }
  }

  private repositionDropdown(): void {
    cancelAnimationFrame(this.dropdownRAF);
    this.dropdownRAF = requestAnimationFrame(() => {
      if (!this.dropdownEl || !this.activeDropdownTarget) return;
      const btn = this.activeDropdownTarget === 'from' ? this.fromBtn : this.toBtn;
      this.positionDropdown(btn);
    });
  }

  private buildDropdown(target: 'from' | 'to'): HTMLElement {
    const currencies = window.crtodayConverterCurrencies ?? [];

    const availableCodes = new Set<string>(Object.keys(this.data.rates));
    availableCodes.add(this.data.base);
    const available = currencies.filter((c: CurrencyInfo) => availableCodes.has(c.code));

    const dropdown = document.createElement('div');
    dropdown.className = 'crtoday-conv-dropdown';
    dropdown.setAttribute('role', 'listbox');
    dropdown.setAttribute('aria-label', this.getUiLabels().selectCurrency ?? '');

    const searchWrap = document.createElement('div');
    searchWrap.className = 'crtoday-conv-search-wrap';
    const searchInput = document.createElement('input');
    searchInput.type = 'text';
    searchInput.className = 'crtoday-conv-search';
    searchInput.placeholder = this.getUiLabels().searchCurrency ?? '';
    searchInput.setAttribute('aria-label', this.getUiLabels().searchCurrencies ?? '');
    searchWrap.appendChild(searchInput);
    dropdown.appendChild(searchWrap);

    const listEl = document.createElement('div');
    listEl.className = 'crtoday-conv-dropdown-list';
    dropdown.appendChild(listEl);

    const currentCode = target === 'from' ? this.fromCode : this.toCode;

    let focusedIdx = -1;

    const renderOptions = (filter: string) => {
      listEl.innerHTML = '';
      focusedIdx = -1;
      const query = filter.toLowerCase();
      const filtered = query
        ? available.filter(
            (c: CurrencyInfo) => c.code.toLowerCase().includes(query) || c.name.toLowerCase().includes(query)
          )
        : available;

      const codesInView = new Set(filtered.map((c: CurrencyInfo) => c.code));
      let recentCodes = new Set<string>();

      if (!query) {
        const recent = getRecentCurrencies().filter((c) => codesInView.has(c));
        recentCodes = new Set(recent);
        if (recent.length > 0) {
          const group = this.createGroup('recent');
          for (const code of recent) {
            const info = filtered.find((c: CurrencyInfo) => c.code === code);
            if (info) {
              group.appendChild(this.createOption(info, currentCode));
            }
          }
          listEl.appendChild(group);
        }
      }

      for (const gKey of this.getGroupOrder()) {
        const items = filtered.filter(
          (c: CurrencyInfo) => c.group === gKey && (query !== '' || !recentCodes.has(c.code))
        );
        if (items.length === 0) continue;

        const group = this.createGroup(gKey);
        for (const item of items) {
          group.appendChild(this.createOption(item, currentCode));
        }
        listEl.appendChild(group);
      }
    };

    renderOptions('');
    searchInput.addEventListener('input', () => renderOptions(searchInput.value));

    const getFocusableOptions = (): HTMLElement[] =>
      Array.from(listEl.querySelectorAll<HTMLElement>('.crtoday-conv-option'));

    const moveFocus = (delta: number) => {
      const options = getFocusableOptions();
      if (options.length === 0) return;
      focusedIdx = Math.max(0, Math.min(options.length - 1, focusedIdx + delta));
      options[focusedIdx].scrollIntoView({ block: 'nearest' });
      options.forEach((o, i) => o.classList.toggle('crtoday-conv-option-focused', i === focusedIdx));
    };

    dropdown.addEventListener('keydown', (e: KeyboardEvent) => {
      if (e.key === 'ArrowDown') {
        e.preventDefault();
        moveFocus(1);
      } else if (e.key === 'ArrowUp') {
        e.preventDefault();
        moveFocus(-1);
      } else if (e.key === 'Enter') {
        e.preventDefault();
        const options = getFocusableOptions();
        if (focusedIdx >= 0 && focusedIdx < options.length) {
          const code = options[focusedIdx].dataset.code;
          if (code) this.selectCurrency(target, code);
        }
      } else if (e.key === 'Escape') {
        e.preventDefault();
        this.closeDropdown();
        (target === 'from' ? this.fromBtn : this.toBtn).focus();
      }
    });

    listEl.addEventListener('click', (e) => {
      const option = (e.target as HTMLElement).closest<HTMLElement>('.crtoday-conv-option');
      if (option?.dataset.code) {
        this.selectCurrency(target, option.dataset.code);
      }
    });

    dropdown.addEventListener('click', (e) => e.stopPropagation());

    return dropdown;
  }

  private createGroup(key: string): HTMLElement {
    const group = document.createElement('div');
    group.className = 'crtoday-conv-group';
    group.dataset.group = key;
    const label = document.createElement('div');
    label.className = 'crtoday-conv-group-label';
    label.textContent = this.getGroupLabels()[key] || key;
    group.appendChild(label);
    return group;
  }

  private createOption(info: CurrencyInfo, currentCode: string): HTMLElement {
    const opt = document.createElement('div');
    opt.className = 'crtoday-conv-option';
    opt.setAttribute('role', 'option');
    opt.setAttribute('aria-selected', info.code === currentCode ? 'true' : 'false');
    opt.dataset.code = info.code;

    if (info.code === currentCode) {
      opt.classList.add('crtoday-conv-option-selected');
    }

    if (flagBaseUrl() && info.flag) {
      const flagWrap = createFlagWrap(flagBaseUrl(), info.flag, 'crtoday-flag-wrap-sm', 20);
      if (flagWrap) {
        opt.appendChild(flagWrap);
      }
    }

    const code = document.createElement('span');
    code.className = 'crtoday-conv-opt-code';
    code.textContent = info.code;
    opt.appendChild(code);

    const name = document.createElement('span');
    name.className = 'crtoday-conv-opt-name';
    name.textContent = info.name;
    opt.appendChild(name);

    if (info.code === currentCode) {
      const check = document.createElement('span');
      check.className = 'crtoday-conv-opt-check';
      check.setAttribute('aria-hidden', 'true');

      const checkIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
      checkIcon.setAttribute('viewBox', '0 0 24 24');
      checkIcon.setAttribute('fill', 'currentColor');
      checkIcon.setAttribute('focusable', 'false');
      checkIcon.setAttribute('aria-hidden', 'true');
      checkIcon.classList.add('crtoday-conv-opt-check-icon');

      const checkPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
      checkPath.setAttribute('d', CHECK_ICON_PATH);
      checkIcon.appendChild(checkPath);
      check.appendChild(checkIcon);
      opt.appendChild(check);
    }

    return opt;
  }

  private selectCurrency(target: 'from' | 'to', code: string): void {
    if (target === 'from') {
      this.fromCode = code;
      this.el.dataset.crtodayFrom = code;
      this.updateCurrencyButton(this.fromBtn, code);
      this.fromInput.setAttribute('aria-label', this.formatAmountAriaLabel(code));
    } else {
      this.toCode = code;
      this.el.dataset.crtodayTo = code;
      this.updateCurrencyButton(this.toBtn, code);
      this.toInput.setAttribute('aria-label', this.formatAmountAriaLabel(code));
    }

    addRecentCurrency(code);
    this.closeDropdown();
    this.updatePricingPresentation();
    this.onFromInput();

    (target === 'from' ? this.fromBtn : this.toBtn).focus();
  }

  private updateCurrencyButton(btn: HTMLButtonElement, code: string): void {
    const codeSpan = btn.querySelector('.crtoday-conv-code');
    if (codeSpan) codeSpan.textContent = code;

    const flagWrap = btn.querySelector<HTMLElement>('.crtoday-flag-wrap');
    const flag = btn.querySelector<HTMLImageElement>('.crtoday-flag');
    const currencies = window.crtodayConverterCurrencies ?? [];
    const info = currencies.find((c: CurrencyInfo) => c.code === code);

    if (flagWrap && flag && info?.flag && flagBaseUrl()) {
      flagWrap.style.display = '';
      flag.src = `${flagBaseUrl()}${info.flag}.webp`;
      flag.style.display = '';
      return;
    }

    if (flagWrap) {
      flagWrap.style.display = 'none';
    }
  }
}

export function initConverters(): void {
  document.querySelectorAll<HTMLElement>('.crtoday-converter').forEach((el) => {
    if (el.dataset.crtodayInit) return;

    try {
      new FxConverter(el);
      el.dataset.crtodayInit = '1';
    } catch (error) {
      if (typeof window !== 'undefined' && typeof window.console !== 'undefined') {
        window.console.warn(error);
      }
    }
  });
}
