import {
  makeTailwindTokenResolvedFact,
  type FactIndex,
  type TailwindResolvedValue,
  type TailwindTokenResolvedFact,
  type ThemeDeclarationFact,
} from "@fragments-sdk/core";

import { tailwindV4Defaults, type TailwindDefaultsSnapshot } from "./defaults/index.js";

type Category =
  | "color"
  | "spacing"
  | "radius"
  | "shadow"
  | "font-family"
  | "font-size"
  | "duration";

interface ThemeIndexEntry {
  value: string;
  inline: boolean;
}

export type ThemeIndex = ReadonlyMap<string, ThemeIndexEntry>;

export function buildThemeIndex(declarations: readonly ThemeDeclarationFact[]): ThemeIndex {
  const out = new Map<string, ThemeIndexEntry>();
  let warnedConflict = false;
  for (const decl of declarations) {
    const existing = out.get(decl.name);
    if (existing && existing.value !== decl.value && !warnedConflict) {
      warnedConflict = true;
      console.warn(
        `Tailwind theme declaration conflict for ${decl.name}; using the last declaration in source order.`
      );
    }
    out.set(decl.name, {
      value: decl.value,
      inline: decl.inline,
    });
  }
  return out;
}

export function resolveTailwindTokenFacts(
  ix: FactIndex,
  defaults: TailwindDefaultsSnapshot = tailwindV4Defaults
): TailwindTokenResolvedFact[] {
  const themeIndex = buildThemeIndex(ix.byKind("theme_declaration"));
  const seen = new Set<string>();
  const out: TailwindTokenResolvedFact[] = [];

  for (const klass of ix.byKind("tailwind_class")) {
    if (klass.value.kind !== "token") continue;
    const key = `${klass.utility}\0${klass.value.token}`;
    if (seen.has(key)) continue;
    seen.add(key);

    out.push(
      makeTailwindTokenResolvedFact({
        utility: klass.utility,
        token: klass.value.token,
        resolved: resolveTailwindToken({
          utility: klass.utility,
          token: klass.value.token,
          themeIndex,
          defaults,
        }),
      })
    );
  }

  return out;
}

export function resolveTailwindToken(input: {
  utility: string;
  token: string;
  themeIndex: ThemeIndex;
  defaults?: TailwindDefaultsSnapshot;
}): TailwindResolvedValue {
  const defaults = input.defaults ?? tailwindV4Defaults;
  for (const category of categoriesForUtility(input.utility, input.token)) {
    const themeName = `--${category}-${input.token}`;
    const theme = input.themeIndex.get(themeName);
    if (theme) {
      return {
        kind: resolvedKindForCategory(category),
        value: theme.value,
        source: "theme-css",
        ...(theme.inline ? { inline: true } : {}),
      };
    }

    if (category === "spacing") {
      const spacingBase = input.themeIndex.get("--spacing");
      if (spacingBase) {
        const computed = multiplyLength(spacingBase.value, input.token);
        if (computed) {
          return {
            kind: "length",
            value: computed,
            source: "theme-css",
            ...(spacingBase.inline ? { inline: true } : {}),
          };
        }
      }
    }

    const defaultValue = defaults[category]?.[input.token];
    if (defaultValue) {
      return {
        kind: resolvedKindForCategory(category),
        value: defaultValue,
        source: "default",
      };
    }
  }

  return { kind: "unknown", source: "unknown" };
}

function categoriesForUtility(utility: string, token: string): Category[] {
  if (isColorUtility(utility)) {
    return utility === "text" && FONT_SIZE_TOKENS.has(token)
      ? ["font-size", "color"]
      : ["color", "font-size"];
  }
  if (SPACING_UTILITIES.has(utility)) return ["spacing"];
  if (utility === "rounded" || utility.startsWith("rounded-")) return ["radius"];
  if (utility === "shadow") return ["shadow", "color"];
  if (utility === "font") return ["font-family"];
  if (utility === "duration") return ["duration"];
  return [];
}

function resolvedKindForCategory(
  category: Category
): Exclude<TailwindResolvedValue["kind"], "unknown"> {
  switch (category) {
    case "color":
      return "color";
    case "spacing":
      return "length";
    case "radius":
      return "radius";
    case "shadow":
      return "shadow";
    case "font-family":
      return "font-family";
    case "font-size":
      return "font-size";
    case "duration":
      return "duration";
  }
}

function multiplyLength(base: string, token: string): string | null {
  const multiplier = Number(token);
  if (!Number.isFinite(multiplier)) return null;
  const match = /^(-?\d+(?:\.\d+)?)(px|rem|em)$/i.exec(base.trim());
  if (!match) return null;
  const value = Number(match[1]) * multiplier;
  return `${trimNumber(value)}${match[2].toLowerCase()}`;
}

function trimNumber(value: number): string {
  return Number.isInteger(value) ? String(value) : String(Number(value.toFixed(6)));
}

function isColorUtility(utility: string): boolean {
  return COLOR_UTILITIES.has(utility) || utility.startsWith("border-");
}

const COLOR_UTILITIES = new Set([
  "accent",
  "bg",
  "border",
  "caret",
  "decoration",
  "divide",
  "fill",
  "from",
  "outline",
  "placeholder",
  "ring",
  "ring-offset",
  "stroke",
  "text",
  "to",
  "via",
]);

const SPACING_UTILITIES = new Set([
  "m",
  "mt",
  "mr",
  "mb",
  "ml",
  "ms",
  "me",
  "mx",
  "my",
  "p",
  "pt",
  "pr",
  "pb",
  "pl",
  "ps",
  "pe",
  "px",
  "py",
  "gap",
  "gap-x",
  "gap-y",
  "space-x",
  "space-y",
]);

const FONT_SIZE_TOKENS = new Set(["xs", "sm", "base", "lg", "xl", "2xl"]);
