import type { DevToolbarCategory, DevToolbarIssue, DevToolbarSeverity } from "../types.ts";

let issueCounter = 0;

export function getStableSelector(element: Element): string {
  if (element.id) return `#${CSS.escape(element.id)}`;
  const parts: string[] = [];
  let current: Element | null = element;
  while (current && current !== document.documentElement && parts.length < 5) {
    let part = current.tagName.toLowerCase();
    const classes = [...current.classList]
      .slice(0, 2)
      .map((value) => `.${CSS.escape(value)}`)
      .join("");
    part += classes;
    const parent: Element | null = current.parentElement;
    if (parent) {
      const same = [...parent.children].filter((child) => child.tagName === current!.tagName);
      if (same.length > 1) part += `:nth-of-type(${same.indexOf(current) + 1})`;
    }
    parts.unshift(part);
    current = parent;
  }
  return parts.join(" > ");
}

export function createFingerprint(
  ruleId: string,
  selector = "",
  source = "",
  message = "",
): string {
  return `${ruleId}|${location.pathname}|${selector}|${source}|${message}`;
}

export function createIssue(input: {
  ruleId: string;
  category: DevToolbarCategory;
  severity: DevToolbarSeverity;
  title: string;
  message: string;
  element?: Element;
  recommendation?: string;
  explanation?: string;
  confidence?: "high" | "medium" | "low";
  metadata?: Record<string, unknown>;
}): DevToolbarIssue {
  const selector = input.element ? getStableSelector(input.element) : undefined;
  const source = input.element?.getAttribute("data-wrnexus-source") ?? undefined;
  return {
    id: `wrn-issue-${Date.now()}-${++issueCounter}`,
    ruleId: input.ruleId,
    category: input.category,
    severity: input.severity,
    title: input.title,
    message: input.message,
    recommendation: input.recommendation,
    explanation: input.explanation,
    confidence: input.confidence ?? "high",
    target: input.element
      ? {
          selector,
          tagName: input.element.tagName.toLowerCase(),
          id: input.element.id || undefined,
          classes: [...input.element.classList],
          text: input.element.textContent?.trim().slice(0, 120),
        }
      : undefined,
    source: source ? parseSource(source) : undefined,
    metadata: input.metadata,
    fingerprint: createFingerprint(input.ruleId, selector, source, input.message),
    createdAt: Date.now(),
  };
}

export function parseSource(value: string) {
  const match = /^(.*?):(\d+)(?::(\d+))?$/.exec(value);
  if (!match) return { file: value };
  return {
    file: match[1],
    line: Number(match[2]),
    column: match[3] ? Number(match[3]) : undefined,
  };
}

export function isVisible(element: Element): boolean {
  const style = getComputedStyle(element);
  const rect = element.getBoundingClientRect();
  return (
    style.display !== "none" &&
    style.visibility !== "hidden" &&
    Number(style.opacity) !== 0 &&
    rect.width > 0 &&
    rect.height > 0
  );
}

export function accessibleName(element: Element): string {
  const labelledBy = element.getAttribute("aria-labelledby");
  if (labelledBy) {
    const value = labelledBy
      .split(/\s+/)
      .map((id) => document.getElementById(id)?.textContent?.trim() ?? "")
      .join(" ")
      .trim();
    if (value) return value;
  }
  return (
    element.getAttribute("aria-label")?.trim() ||
    element.getAttribute("alt")?.trim() ||
    element.getAttribute("title")?.trim() ||
    element.textContent?.trim() ||
    ""
  );
}

export function parseRgb(input: string): [number, number, number, number] | null {
  const match = input.match(/rgba?\(([^)]+)\)/i);
  if (!match) return null;
  const parts = match[1]
    .split(/[,/ ]+/)
    .filter(Boolean)
    .map(Number);
  if (parts.length < 3 || parts.some(Number.isNaN)) return null;
  return [parts[0], parts[1], parts[2], parts[3] ?? 1];
}

export function luminance([r, g, b]: [number, number, number, number]): number {
  const values = [r, g, b].map((v) => {
    const c = v / 255;
    return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
  });
  return values[0] * 0.2126 + values[1] * 0.7152 + values[2] * 0.0722;
}

export function contrastRatio(
  a: [number, number, number, number],
  b: [number, number, number, number],
): number {
  const l1 = luminance(a);
  const l2 = luminance(b);
  return (Math.max(l1, l2) + 0.05) / (Math.min(l1, l2) + 0.05);
}

export function effectiveBackground(element: Element): [number, number, number, number] | null {
  let current: Element | null = element;
  while (current) {
    const parsed = parseRgb(getComputedStyle(current).backgroundColor);
    if (parsed && parsed[3] > 0.01) return parsed;
    current = current.parentElement;
  }
  return [255, 255, 255, 1];
}
