// Simple palette extraction using Canvas API (no external deps)
// Returns up to 5 representative colors as hex strings
import { API_URL } from '../services/api';

export async function extractPalette(imageUrl: string, maxColors = 12): Promise<string[]> {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.crossOrigin = 'anonymous';
    img.onload = () => {
      try {
        const canvas = document.createElement('canvas');
        const ctx = canvas.getContext('2d');
        if (!ctx) return resolve([]);
        // Downscale for performance
        const w = 160; // slightly larger for better color sampling
        const h = Math.max(1, Math.round((img.height / img.width) * w));
        canvas.width = w;
        canvas.height = h;
        ctx.drawImage(img, 0, 0, w, h);
        const { data } = ctx.getImageData(0, 0, w, h);
        const map: Record<string, number> = {};
        for (let i = 0; i < data.length; i += 4) {
          const r = data[i];
          const g = data[i + 1];
          const b = data[i + 2];
          const a = data[i + 3];
          if (a < 200) continue; // skip transparent
          // Slightly finer quantization to keep important hues
          const quant = 16;
          const rq = Math.round(r / quant) * quant;
          const gq = Math.round(g / quant) * quant;
          const bq = Math.round(b / quant) * quant;
          const key = `${rq},${gq},${bq}`;
          map[key] = (map[key] || 0) + 1;
        }
        const entries = Object.entries(map).sort((a, b) => b[1] - a[1]);
        // Merge very similar colors to increase distinctiveness
        const merged: Array<{ key: string; count: number }> = [];
  const colorDist = (a: number[], b: number[]) => Math.sqrt((a[0]-b[0])**2 + (a[1]-b[1])**2 + (a[2]-b[2])**2);
        for (const [k, cnt] of entries) {
          const [r, g, b] = k.split(',').map(Number);
          let found = false;
          for (const m of merged) {
            const [mr, mg, mb] = m.key.split(',').map(Number);
            if (colorDist([r,g,b],[mr,mg,mb]) < 24) {
              m.count += cnt;
              found = true;
              break;
            }
          }
          if (!found) merged.push({ key: k, count: cnt });
        }
        merged.sort((a,b) => b.count - a.count);
        const toHex = (n: number) => Math.max(0, Math.min(255, n)).toString(16).padStart(2, '0');
        const colors: string[] = merged.slice(0, maxColors).map(m => {
          const [r,g,b] = m.key.split(',').map(Number);
          return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
        });
        // If palette is too uniform (e.g., many near-same colors), try to pick more varied ones by spacing selection
        if (colors.length > 1) {
          const unique: string[] = [];
          for (const c of colors) {
            const cr = parseInt(c.slice(1,3),16), cg = parseInt(c.slice(3,5),16), cb = parseInt(c.slice(5,7),16);
            if (!unique.some(u => {
              const ur = parseInt(u.slice(1,3),16), ug = parseInt(u.slice(3,5),16), ub = parseInt(u.slice(5,7),16);
              return colorDist([ur,ug,ub],[cr,cg,cb]) < 40;
            })) {
              unique.push(c);
            }
            if (unique.length >= maxColors) break;
          }
          if (unique.length) resolve(unique);
          else resolve(colors);
        } else {
          resolve(colors);
        }
      } catch (e) {
        resolve([]);
      }
    };
    img.onerror = () => resolve([]);
    // Route through backend (or proxy) to bypass CORS and handle root-relative URLs from backend
    try {
      // Derive backend origin from API_URL (which points to /api/v1)
      const api = new URL(API_URL);
      const backendOrigin = api.origin; // e.g., http://localhost:8080

      // If the URL is backend-relative (e.g., /uploads, /dist, /api), prefix with backend origin
      let candidate = imageUrl || '';
      if (candidate.startsWith('/uploads') || candidate.startsWith('/dist') || candidate.startsWith('/api/')) {
        candidate = backendOrigin + candidate;
      }

      const u = new URL(candidate, window.location.origin);
      const isData = u.protocol === 'data:';
      const sameOriginAsWindow = u.origin === window.location.origin;
      const sameOriginAsBackend = u.origin === backendOrigin;

      // Use direct URL if it's same-origin with either the window (served by dev server) or backend (static uploads)
      if (isData || sameOriginAsWindow || sameOriginAsBackend) {
        img.src = u.toString();
      } else {
        // Otherwise, use backend proxy to obtain CORS-eligible bytes for Canvas
        img.src = `${API_URL}/proxy/image?url=${encodeURIComponent(u.toString())}`;
      }
    } catch {
      // Fallback to original URL if parsing fails
      img.src = imageUrl;
    }
  });
}

export function pickTextColor(bgHex: string): string {
  // YIQ contrast
  const hex = bgHex.replace('#', '');
  const r = parseInt(hex.substring(0, 2), 16);
  const g = parseInt(hex.substring(2, 4), 16);
  const b = parseInt(hex.substring(4, 6), 16);
  const yiq = (r * 299 + g * 587 + b * 114) / 1000;
  return yiq >= 128 ? '#000000' : '#ffffff';
}

// WCAG contrast helpers
function srgbToLinear(c: number): number {
  const v = c / 255;
  return v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
}

function relLuminance(hex: string): number {
  const h = hex.replace('#', '');
  const r = parseInt(h.substring(0, 2), 16);
  const g = parseInt(h.substring(2, 4), 16);
  const b = parseInt(h.substring(4, 6), 16);
  const R = srgbToLinear(r);
  const G = srgbToLinear(g);
  const B = srgbToLinear(b);
  return 0.2126 * R + 0.7152 * G + 0.0722 * B;
}

export function contrastRatio(hex1: string, hex2: string): number {
  try {
    const L1 = relLuminance(hex1);
    const L2 = relLuminance(hex2);
    const lighter = Math.max(L1, L2);
    const darker = Math.min(L1, L2);
    return (lighter + 0.05) / (darker + 0.05);
  } catch {
    return 1;
  }
}

export function isContrastAccessible(hex1: string, hex2: string, level: 'AA' | 'AAA' = 'AA'): boolean {
  const ratio = contrastRatio(hex1, hex2);
  const threshold = level === 'AAA' ? 7 : 4.5; // normal text thresholds
  return ratio >= threshold;
}

export function generateJwtSecret(length = 48): string {
  const bytes = new Uint8Array(length);
  window.crypto.getRandomValues(bytes);
  // Base64url encode
  const base64 = btoa(String.fromCharCode(...Array.from(bytes)));
  return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}

// --- Theme generation helpers ---

function clamp01(x: number) { return Math.min(1, Math.max(0, x)); }

function hexToRgb(hex: string): { r: number; g: number; b: number } {
  const h = hex.replace('#', '');
  const r = parseInt(h.substring(0, 2), 16);
  const g = parseInt(h.substring(2, 4), 16);
  const b = parseInt(h.substring(4, 6), 16);
  return { r, g, b };
}

function rgbToHex(r: number, g: number, b: number): string {
  const toHex = (n: number) => Math.round(clamp01(n) * 255).toString(16).padStart(2, '0');
  return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
}

function lighten(hex: string, amt = 0.15): string {
  const { r, g, b } = hexToRgb(hex);
  return rgbToHex(r / 255 + amt, g / 255 + amt, b / 255 + amt);
}

function darken(hex: string, amt = 0.15): string {
  const { r, g, b } = hexToRgb(hex);
  return rgbToHex(r / 255 - amt, g / 255 - amt, b / 255 - amt);
}

export function adjustForContrast(bg: string, preferredText?: string): { text: string; bg: string } {
  const text = preferredText || pickTextColor(bg);
  if (isContrastAccessible(bg, text, 'AA')) return { text, bg };
  // try darken/lighten background
  for (const amt of [0.1, 0.2, 0.3]) {
    const darker = darken(bg, amt);
    if (isContrastAccessible(darker, text, 'AA')) return { text, bg: darker };
    const lighter = lighten(bg, amt);
    if (isContrastAccessible(lighter, text, 'AA')) return { text, bg: lighter };
  }
  // fallback: flip text color
  const flipped = text === '#000000' ? '#ffffff' : '#000000';
  return { text: flipped, bg };
}

export type ThemeCandidate = {
  name: string;
  primary: string;
  secondary: string;
  accent: string;
  background: string;
  text: string;
};

// Generate up to 5 theme candidates from extracted palette, tuned for readability.
export function generateThemeCandidates(palette: string[]): ThemeCandidate[] {
  const p = (palette || []).filter(Boolean);
  // Fallback palette
  const fallback = ['#0b5cff', '#ffd200', '#141414', '#ffffff'];

  // Helper: convert hex to HSL
  function hexToHsl(hex: string) {
    const { r, g, b } = hexToRgb(hex);
    const rn = r / 255, gn = g / 255, bn = b / 255;
    const max = Math.max(rn, gn, bn), min = Math.min(rn, gn, bn);
    let h = 0, s = 0, l = (max + min) / 2;
    if (max !== min) {
      const d = max - min;
      s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
      switch (max) {
        case rn: h = (gn - bn) / d + (gn < bn ? 6 : 0); break;
        case gn: h = (bn - rn) / d + 2; break;
        case bn: h = (rn - gn) / d + 4; break;
      }
      h /= 6;
    }
    return { h, s, l };
  }

  // Choose a contrasting accent from palette relative to a base
  function pickAccent(base: string, arr: string[]) {
    if (!arr || arr.length === 0) return fallback[2];
    // pick color with highest contrast ratio against base
    let best = arr[0];
    let bestScore = contrastRatio(base, best);
    for (const c of arr) {
      const score = contrastRatio(base, c);
      if (score > bestScore) { bestScore = score; best = c; }
    }
    return best;
  }

  const candidates: ThemeCandidate[] = [];

  // Helper to push candidate and ensure accessibility
  const pushCandidate = (name: string, primary: string, secondary: string, accentColor: string, background: string) => {
    const { text } = adjustForContrast(background, '#111111');
    const fixed = adjustForContrast(background, text);
    candidates.push({ name, primary, secondary, accent: accentColor, background: fixed.bg, text: fixed.text });
  };

  // Prepare palette with fallbacks
  const usePalette = p.length ? p : fallback;

  // 1) Dominant: use most frequent as primary, next distinct as secondary
  const dominant = usePalette[0] || fallback[0];
  const nextDistinct = usePalette.find((c) => c.toLowerCase() !== dominant.toLowerCase()) || fallback[1];
  const accent1 = pickAccent(dominant, usePalette.slice(1)) || fallback[2];
  pushCandidate('Klasický', dominant, nextDistinct, accent1, '#ffffff');

  // 2) Vibrant: pick color with highest saturation as primary
  const withSat = usePalette.map((c) => ({ c, s: hexToHsl(c).s }));
  withSat.sort((a, b) => b.s - a.s);
  const vibrant = withSat[0]?.c || dominant;
  const vibrant2 = withSat[1]?.c || nextDistinct;
  const accent2 = pickAccent(vibrant, palette) || accent1;
  pushCandidate('Živý', vibrant, vibrant2, accent2, '#ffffff');

  // 3) Muted: pick a less-saturated or darker color for primary and a light background
  const muted = withSat.slice(-1)[0]?.c || usePalette[1] || fallback[1];
  const mutedSecondary = usePalette.find((c) => c !== muted) || usePalette[0] || fallback[1];
  const accent3 = pickAccent(muted, usePalette) || fallback[2];
  pushCandidate('Tlumený', muted, mutedSecondary, accent3, '#f8f9fb');

  // 4) Dark: primary = dominant, background dark
  const darkAccent = pickAccent('#0f1115', usePalette) || accent1;
  pushCandidate('Tmavý', dominant, nextDistinct, darkAccent, '#0f1115');

  // 5) Accent-forward: choose the most contrasting color overall as accent, keep primary neutral
  // Find color with highest average contrast to others
  let bestAccent = usePalette[0] || fallback[2];
  let bestScore = -1;
  for (const c of usePalette) {
    let sum = 0;
    for (const d of usePalette) sum += contrastRatio(c, d);
    if (sum > bestScore) { bestScore = sum; bestAccent = c; }
  }
  const accentForwardPrimary = dominant;
  const accentForwardSecondary = usePalette.find((c) => c !== bestAccent) || nextDistinct;
  pushCandidate('Akcentní', accentForwardPrimary, accentForwardSecondary, bestAccent, '#ffffff');

  return candidates;
}
