import type { AsukaCustomFontConfig, AsukaGoogleFontConfig, AsukaRegisteredFont, AsukaSystemFontConfig } from '../types/options';

const DEFAULT_SYSTEM_FONTS: AsukaSystemFontConfig[] = [
  { family: 'Arial' },
  { family: 'Helvetica' },
  { family: 'Times New Roman' },
  { family: 'Georgia' },
  { family: 'Courier New' },
];

const injectedLinkIds = new Set<string>();
const loadedFaceKeys = new Set<string>();
const stylesheetLoadPromises = new Map<string, Promise<boolean>>();
const FONT_LOAD_TIMEOUT_MS = 15_000;

function safeId(prefix: string, raw: string): string {
  const clean = String(raw ?? '').trim().toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
  return `${prefix}-${clean || Math.random().toString(36).slice(2, 8)}`;
}

function familyToGoogleCssUrl(family: string, weights?: Array<string | number>, styles?: Array<'normal' | 'italic'>): string {
  const fam = encodeURIComponent(String(family ?? '').trim()).replace(/%20/g, '+');
  const normalizedWeights = Array.from(new Set(
    (Array.isArray(weights) && weights.length ? weights : [400])
      .map((weight) => String(weight).trim())
      .filter(Boolean),
  ));
  const normalizedStyles = Array.from(new Set(Array.isArray(styles) && styles.length ? styles : ['normal']));
  const wantItalicAxis = normalizedStyles.includes('italic');
  const familyParam = wantItalicAxis
    ? `${fam}:ital,wght@${normalizedStyles.flatMap((style) => normalizedWeights.map((weight) => `${style === 'italic' ? 1 : 0},${weight}`)).join(';')}`
    : `${fam}:wght@${normalizedWeights.join(';')}`;
  return `https://fonts.googleapis.com/css2?family=${familyParam}&display=swap`;
}

export function normalizeSystemFont(input: string | AsukaSystemFontConfig): AsukaRegisteredFont {
  const family = typeof input === 'string' ? input : input.family;
  return {
    id: typeof input === 'string' ? safeId('font-system', family) : (input.id ? String(input.id) : safeId('font-system', family)),
    family: String(family ?? '').trim(),
    source: 'system',
    loaded: true,
    loading: false,
  };
}

export function normalizeGoogleFont(input: string | AsukaGoogleFontConfig): AsukaRegisteredFont {
  const cfg = typeof input === 'string' ? { family: input } : input;
  const family = String(cfg.family ?? '').trim();
  return {
    id: cfg.id ? String(cfg.id) : safeId('font-google', family),
    family,
    source: 'google',
    cssUrl: cfg.cssUrl || familyToGoogleCssUrl(family, cfg.weights, cfg.styles),
    weights: cfg.weights?.map((v) => String(v)),
    styles: cfg.styles,
    loaded: false,
    loading: false,
  };
}

export function normalizeCustomFont(input: AsukaCustomFontConfig): AsukaRegisteredFont {
  const family = String(input.family ?? '').trim();
  return {
    id: input.id ? String(input.id) : safeId('font-custom', family),
    family,
    source: 'custom',
    cssUrl: input.cssUrl,
    url: input.url,
    format: input.format,
    weight: input.weight,
    style: input.style,
    loaded: false,
    loading: false,
  };
}

export function getDefaultSystemFonts(): AsukaRegisteredFont[] {
  return DEFAULT_SYSTEM_FONTS.map(normalizeSystemFont);
}

export function dedupeFonts(fonts: AsukaRegisteredFont[]): AsukaRegisteredFont[] {
  const seen = new Map<string, AsukaRegisteredFont>();
  for (const font of fonts) {
    if (!font || !String(font.family ?? '').trim()) continue;
    const key = `${String(font.id ?? '').trim()}::${String(font.family ?? '').trim().toLowerCase()}::${String(font.source ?? 'system')}`;
    if (!seen.has(key)) seen.set(key, font);
  }
  return Array.from(seen.values());
}

function withBooleanTimeout(promise: Promise<boolean>, timeoutMs = FONT_LOAD_TIMEOUT_MS): Promise<boolean> {
  return new Promise((resolve) => {
    let settled = false;
    const finish = (value: boolean) => {
      if (settled) return;
      settled = true;
      clearTimeout(timeout);
      resolve(value);
    };
    const timeout = setTimeout(() => finish(false), timeoutMs);
    promise.then((value) => finish(value)).catch(() => finish(false));
  });
}

function isStylesheetReady(link: HTMLLinkElement | null): boolean {
  if (!link) return false;
  if (link.dataset?.asukaFontLoaded === 'true') return true;
  try { return Boolean(link.sheet); } catch { return false; }
}

function ensureStylesheet(linkId: string, href: string): Promise<boolean> {
  if (typeof document === 'undefined' || !href) return Promise.resolve(false);
  const cached = stylesheetLoadPromises.get(linkId);
  if (cached) return cached;

  const pending = withBooleanTimeout(new Promise<boolean>((resolve) => {
    let link = document.getElementById(linkId) as HTMLLinkElement | null;
    if (isStylesheetReady(link)) {
      injectedLinkIds.add(linkId);
      resolve(true);
      return;
    }

    const created = !link;
    if (!link) {
      link = document.createElement('link');
      link.id = linkId;
      link.rel = 'stylesheet';
      link.href = href;
    }

    const finish = (loaded: boolean) => {
      if (loaded && link) link.dataset.asukaFontLoaded = 'true';
      if (loaded) injectedLinkIds.add(linkId);
      resolve(loaded);
    };
    link.addEventListener('load', () => finish(true), { once: true });
    link.addEventListener('error', () => finish(false), { once: true });
    if (created) document.head.appendChild(link);
  }));

  const tracked = pending.then((loaded) => {
    if (!loaded) stylesheetLoadPromises.delete(linkId);
    return loaded;
  });
  stylesheetLoadPromises.set(linkId, tracked);
  return tracked;
}

async function waitForFontSet(font: AsukaRegisteredFont): Promise<boolean> {
  const fontSet = (document as any)?.fonts;
  if (!fontSet || typeof fontSet.load !== 'function') return true;
  const weights = font.weights?.length ? font.weights : [font.weight ?? 400];
  const styles = font.styles?.length ? font.styles : [font.style ?? 'normal'];
  const family = String(font.family).replace(/(["\\])/g, '\\$1');
  const queries = styles.flatMap((style) => weights.map((weight) => `${style} ${weight} 16px "${family}"`));
  return await withBooleanTimeout(Promise.all(queries.map((query) => fontSet.load(query))).then(() => true));
}

export async function loadRegisteredFont(font: AsukaRegisteredFont): Promise<boolean> {
  if (!font || !String(font.family ?? '').trim()) return false;
  if (font.source === 'system') return true;
  if (typeof window === 'undefined' || typeof document === 'undefined') return false;

  if (font.source === 'google') {
    const href = String(font.cssUrl ?? '').trim();
    if (!href) return false;
    const loaded = await ensureStylesheet(`asuka-font-link-${font.id}`, href);
    return loaded ? await waitForFontSet(font) : false;
  }

  if (font.source === 'custom') {
    if (font.cssUrl) {
      const loaded = await ensureStylesheet(`asuka-font-link-${font.id}`, String(font.cssUrl));
      return loaded ? await waitForFontSet(font) : false;
    }
    if (font.url && 'FontFace' in window) {
      const key = `${font.family}::${font.url}::${font.weight ?? 400}::${font.style ?? 'normal'}`;
      if (loadedFaceKeys.has(key)) return true;
      try {
        const escapedUrl = String(font.url).replace(/(["\\])/g, '\\$1');
        const format = font.format ? ` format("${font.format}")` : '';
        const face = new FontFace(font.family, `url("${escapedUrl}")${format}`, {
          style: (font.style as any) || 'normal',
          weight: font.weight != null ? String(font.weight) : '400',
        });
        const loadedFace = await face.load();
        (document as any).fonts?.add?.(loadedFace);
        loadedFaceKeys.add(key);
        return true;
      } catch {
        return false;
      }
    }
  }

  return false;
}
