import * as fs from "node:fs";
import * as path from "node:path";
import type { MigrationContext } from "../types";
import { log } from "../types";
import type { ExtractedTheme } from "../analyzers/theme-extractor";
import { fixOklchHexMismatches, isOklchCoordinates } from "../transforms/color-oklch";
import { transformCss } from "../transforms/css";
import { renameToken } from "../transforms/tailwind-renames";

const GRAY_SHADES = ["50", "100", "200", "300", "400", "500", "600", "700", "800", "900", "950"];
const GRAY_SCALE_DEFAULTS: Record<string, string> = {
  "50": "#f9fafb",
  "100": "#f3f4f6",
  "200": "#e5e7eb",
  "300": "#d1d5db",
  "400": "#9ca3af",
  "500": "#6b7280",
  "600": "#4b5563",
  "700": "#374151",
  "800": "#1f2937",
  "900": "#111827",
  "950": "#030712",
};

/**
 * Find the original site's custom CSS file.
 * Deco sites typically have their custom CSS in:
 * - tailwind.css (root level, combined directives + custom)
 * - static/tailwind.css (compiled output — skip this)
 * - static-{brand}/tailwind.css (compiled output — skip this)
 * - styles/*.css
 */
function findOriginalCss(ctx: MigrationContext): string | null {
  // Prefer root tailwind.css (has custom CSS + directives)
  const rootCss = path.join(ctx.sourceDir, "tailwind.css");
  if (fs.existsSync(rootCss)) {
    return fs.readFileSync(rootCss, "utf-8");
  }

  // Check for styles/ directory
  const stylesDir = path.join(ctx.sourceDir, "styles");
  if (fs.existsSync(stylesDir)) {
    for (const file of fs.readdirSync(stylesDir)) {
      if (file.endsWith(".css") && file !== "tailwind.css") {
        return fs.readFileSync(path.join(stylesDir, file), "utf-8");
      }
    }
  }

  return null;
}

/**
 * Extract custom CSS from the original site's CSS file.
 * Strips TW3 directives (@tailwind base/components/utilities) and
 * returns only the custom CSS (component overrides, @layer, @font-face, etc.)
 */
function extractCustomCss(rawCss: string): string {
  return rawCss
    // Remove Tailwind v3 directives (replaced by @import "tailwindcss")
    .replace(/^@tailwind\s+(?:base|components|utilities)\s*;\s*$/gm, "")
    // Remove empty lines left over
    .replace(/^\s*\n/gm, "")
    .trim();
}

/**
 * Transform @apply directives for TW3→TW4 compatibility.
 * The tailwind.ts transform handles className= attributes in JSX,
 * but @apply inside CSS also needs the same class renames — reuses the
 * single shared table (transforms/tailwind-renames.ts) so this never
 * drifts from the className= rewriter again.
 */
function transformApplyDirectives(css: string): string {
  return css.replace(/@apply\s+([^;]+);/g, (_match, classes: string) => {
    const fixed = classes
      .split(/\s+/)
      .filter(Boolean)
      .map(renameToken)
      .filter(Boolean)
      .join(" ");
    return `@apply ${fixed};`;
  });
}

/**
 * Extract the primary font family from @font-face declarations in CSS.
 * Returns the first font-family name found, or null.
 */
function extractPrimaryFontFromCss(css: string): string | null {
  const fontFaceRe = /@font-face\s*\{[^}]*font-family:\s*["']?([^"';]+)["']?\s*;/g;
  const families = new Set<string>();
  let match;
  while ((match = fontFaceRe.exec(css)) !== null) {
    families.add(match[1].trim());
  }
  if (families.size === 0) return null;
  // Prefer non-icon fonts
  const nonIcon = [...families].filter(
    (f) => !/icon|awesome|material/i.test(f),
  );
  return nonIcon[0] || [...families][0];
}

export function generateAppCss(ctx: MigrationContext, theme?: ExtractedTheme): string {
  const sections: string[] = [];

  // ── DaisyUI theme plugin ──────────────────────────────────────────
  const daisyColors = theme?.daisyUiColors ?? {};
  const c = ctx.themeColors;

  const semanticColors: Record<string, string> = {
    "--color-primary": daisyColors["--color-primary"] || c["primary"] || "#B10200",
    "--color-secondary": daisyColors["--color-secondary"] || c["secondary"] || "#141414",
    "--color-accent": daisyColors["--color-accent"] || c["tertiary"] || "#FFF100",
    "--color-neutral": daisyColors["--color-neutral"] || c["neutral"] || "#393939",
    "--color-base-100": daisyColors["--color-base-100"] || c["base-100"] || "#FFFFFF",
    "--color-base-200": daisyColors["--color-base-200"] || c["base-200"] || "#F3F3F3",
    "--color-base-300": daisyColors["--color-base-300"] || c["base-300"] || "#868686",
    "--color-info": daisyColors["--color-info"] || c["info"] || "#006CA1",
    "--color-success": daisyColors["--color-success"] || c["success"] || "#007552",
    "--color-warning": daisyColors["--color-warning"] || c["warning"] || "#F8D13A",
    "--color-error": daisyColors["--color-error"] || c["error"] || "#CF040A",
  };

  const colorLines = Object.entries(semanticColors)
    .map(([k, v]) => `  ${k}: ${v};`)
    .join("\n");

  sections.push(`@import "tailwindcss";
@plugin "daisyui";
@plugin "daisyui/theme" {
  name: "light";
  default: true;
  color-scheme: light;
${colorLines}
}`);

  // ── @theme block: Tailwind v3->v4 color migration ─────────────────
  // Determine font family from theme, context, or original CSS @font-face
  let fontFamily = theme?.fontFamily || ctx.fontFamily;
  if (!fontFamily) {
    const originalCssForFont = findOriginalCss(ctx);
    if (originalCssForFont) {
      const extracted = extractPrimaryFontFromCss(originalCssForFont);
      if (extracted) fontFamily = extracted;
    }
  }
  let fontLine = "";
  if (fontFamily) {
    const firstFont = fontFamily.split(",")[0].trim().replace(/['"]/g, "");
    fontLine = `\n  --font-sans: "${firstFont}", ui-sans-serif, system-ui, sans-serif;`;
  }

  let themeBlock = `/* Tailwind v4: reset default palette (old sites replaced it entirely via theme.colors)
   then re-add only the colors used by this site. */
@theme {
  --color-*: initial;

  --color-white: #fff;
  --color-black: #000;
  --color-transparent: transparent;
  --color-current: currentColor;
  --color-inherit: inherit;${fontLine}`;

  // Add extracted theme variables to @theme
  // Theme variables come from the CMS Theme section (.deco/blocks/Theme-*.json).
  // The CMS sets CSS custom properties on :root at runtime, so @theme entries
  // should reference var(--x) instead of hardcoding values. For oklch-formatted
  // values (space-separated numbers like "0.5 0.2 30"), wrap with oklch().
  const vars = theme?.variables ?? {};
  if (Object.keys(vars).length > 0) {
    themeBlock += `\n`;
    const grouped: Record<string, Array<[string, string]>> = {};
    for (const [k, v] of Object.entries(vars)) {
      const prefix = k.replace(/^--/, "").split("-").slice(0, 2).join("-");
      if (!grouped[prefix]) grouped[prefix] = [];
      grouped[prefix].push([k, v]);
    }

    for (const [prefix, entries] of Object.entries(grouped)) {
      themeBlock += `\n  /* ${prefix} */`;
      for (const [k, v] of entries) {
        if (!k.startsWith("--color-") && !k.startsWith("--font-") && !k.startsWith("--breakpoint-")) {
          const val = v.trim();
          const isColor = /^#[0-9a-fA-F]{3,8}$/.test(val) ||
            /^(rgb|hsl|oklch|oklab)\(/.test(val) ||
            /^(transparent|currentColor|inherit)$/.test(val) ||
            isOklchCoordinates(val);
          if (isColor) {
            const varName = k.replace(/^--/, "");
            const colorKey = `--color-${varName}`;
            if (isOklchCoordinates(val)) {
              themeBlock += `\n  ${colorKey}: oklch(var(${k}));`;
            } else {
              themeBlock += `\n  ${colorKey}: var(${k});`;
            }
          }
        }
      }
    }
  }

  // Port theme.extend.colors / theme.extend.fontFamily / theme.extend.screens
  // from the source site's tailwind.config.ts (extracted in phase-analyze
  // before that file is deleted — see analyzers/tailwind-config.ts). Without
  // this, custom brand palettes and named font stacks are silently dropped
  // and any class referencing them (`bg-perola`, `font-bebas-neue`) makes
  // Tailwind v4 throw "Cannot apply unknown utility class" (decocms/blocks#369).
  // gray-50..950 are handled below, in the gray-scale block, so a custom
  // gray ramp from tailwind.config.ts overrides the default instead of
  // being declared twice in the same @theme rule (last declaration wins in
  // CSS — the default would otherwise silently clobber the custom one).
  const twColors = ctx.tailwindConfig.colors;
  const twColorKeys = Object.keys(twColors).filter(
    (k) => !(`--color-${k}` in semanticColors) && !GRAY_SHADES.some((shade) => k === `gray-${shade}`),
  );
  if (twColorKeys.length > 0) {
    themeBlock += `\n\n  /* Custom colors ported from tailwind.config.ts */`;
    for (const key of twColorKeys) {
      themeBlock += `\n  --color-${key}: ${twColors[key]};`;
    }
  }

  const twFontFamily = ctx.tailwindConfig.fontFamily;
  const twFontKeys = Object.keys(twFontFamily);
  if (twFontKeys.length > 0) {
    themeBlock += `\n\n  /* Font stacks ported from tailwind.config.ts */`;
    for (const key of twFontKeys) {
      themeBlock += `\n  --font-${key}: ${twFontFamily[key]};`;
    }
  }

  const twScreens = ctx.tailwindConfig.screens;
  const twScreenKeys = Object.keys(twScreens);
  if (twScreenKeys.length > 0) {
    themeBlock += `\n\n  /* Custom breakpoints ported from tailwind.config.ts */`;
    for (const key of twScreenKeys) {
      themeBlock += `\n  --breakpoint-${key}: ${twScreens[key]};`;
    }
  }

  const twAnimations = ctx.tailwindConfig.animations;
  const twAnimationKeys = Object.keys(twAnimations);
  if (twAnimationKeys.length > 0) {
    themeBlock += `\n\n  /* Custom animations ported from tailwind.config.ts */`;
    for (const key of twAnimationKeys) {
      themeBlock += `\n  --animate-${key}: ${twAnimations[key]};`;
    }
  }

  // Gray scale compat (Tailwind v3 had gray-50..gray-950 by default) — uses
  // the site's own tailwind.config.ts gray-* override when present, falling
  // back to the v3 default otherwise.
  themeBlock += `

  /* Gray scale (Tailwind v3 default, required for bg-gray-*, text-gray-*, etc.) */`;
  for (const shade of GRAY_SHADES) {
    const key = `gray-${shade}`;
    const value = twColors[key] ?? GRAY_SCALE_DEFAULTS[shade];
    themeBlock += `\n  --color-${key}: ${value};`;
  }
  themeBlock += `\n}`;
  sections.push(themeBlock);

  // Emit @keyframes for each extracted keyframe from tailwind.config.ts.
  // TW4 has no keyframes config — they must live in CSS directly.
  const twKeyframes = ctx.tailwindConfig.keyframes;
  for (const [name, stops] of Object.entries(twKeyframes)) {
    const stopLines = Object.entries(stops)
      .map(([pct, props]) => `  ${pct} { ${props}; }`)
      .join("\n");
    sections.push(`@keyframes ${name} {\n${stopLines}\n}`);
  }

  // ── DaisyUI v5 compat ─────────────────────────────────────────────
  sections.push(`/* DaisyUI v5: flatten depth/noise to match v4 look */
:root {
  --depth: 0;
  --noise: 0;
}`);

  // ── :root theme variables ─────────────────────────────────────────
  if (Object.keys(vars).length > 0) {
    let rootBlock = `:root {\n`;
    for (const [k, v] of Object.entries(vars)) {
      rootBlock += `  ${k}: ${v};\n`;
    }
    if (fontFamily) {
      const firstFont = fontFamily.split(",")[0].trim().replace(/['"]/g, "");
      rootBlock += `  --font-family: ${firstFont}, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;\n`;
    }
    rootBlock += `}`;
    sections.push(rootBlock);
  }

  // ── DaisyUI v5 carousel overflow fix ──────────────────────────────
  sections.push(`/* DaisyUI v5 removed carousel overflow — re-add for horizontal scroll */
.carousel {
  -webkit-overflow-scrolling: touch;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
}

.carousel > * {
  scroll-snap-align: start;
}

ul.carousel,
ol.carousel {
  list-style: none;
  padding: 0;
}`);

  // ── Container utility (v3 -> v4 migration) ────────────────────────
  sections.push(`/* Container: replaces Tailwind v3 container plugin config */
@utility container {
  margin-inline: auto;
  padding-inline: 1rem;
  width: 100%;

  @media (width >= 640px) { max-width: 640px; }
  @media (width >= 768px) { max-width: 768px; }
  @media (width >= 1024px) { max-width: 1024px; }
  @media (width >= 1280px) { max-width: 1280px; }
  @media (width >= 1536px) { max-width: 1536px; }
}`);

  // ── Deferred section visibility ───────────────────────────────────
  sections.push(`/* Deferred section visibility — reduces layout shift while loading */
section[data-deferred="true"] {
  content-visibility: auto;
  contain-intrinsic-size: auto 300px;
}

.deferred-section {
  content-visibility: auto;
  contain-intrinsic-size: auto 300px;
}`);

  // ── View transitions ──────────────────────────────────────────────
  sections.push(`@view-transition {
  navigation: auto;
}`);

  // ── Base layer resets ─────────────────────────────────────────────
  sections.push(`@layer base {
  *,
  *::before,
  *::after {
    box-sizing: border-box;
  }

  body {
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
  }

  img,
  picture,
  video,
  canvas,
  svg {
    display: block;
    max-width: 100%;
  }

  /* Drawer / modal scroll lock */
  body:has(dialog[open]),
  body:has(.drawer-toggle:checked) {
    overflow: hidden;
  }

  /* Tailwind v3 -> v4 compat: restore the default border-color.
     TW4 Preflight changed border-color from gray-200 (#e5e7eb) to currentColor --
     any element with "border" but no explicit "border-{color}" now renders black.
     --color-gray-200 is always defined in the ported @theme (gray scale compat block). */
  *, *::before, *::after {
    border-color: var(--color-gray-200, #e5e7eb);
  }

  /* Tailwind v3 → v4 compat: restore the default cursor:pointer on interactive
     elements. Tailwind v4 dropped this from Preflight, leaving Fresh-era sites
     with hundreds of buttons looking unclickable. See:
     https://tailwindcss.com/docs/upgrade-guide#default-button-cursor */
  button:not(:disabled),
  [role="button"]:not([aria-disabled="true"]),
  label[for],
  summary {
    cursor: pointer;
  }
  button:disabled,
  [role="button"][aria-disabled="true"] {
    cursor: not-allowed;
  }
}`);

  // ── Scrollbar utility ─────────────────────────────────────────────
  sections.push(`.scrollbar-none {
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.scrollbar-none::-webkit-scrollbar {
  display: none;
}`);

  // ── Safelist (tailwind.config.ts) ──────────────────────────────────
  // Tailwind v4 has no config-based safelist. Literal class names port
  // directly to `@source inline(...)`; regex patterns can't be converted
  // automatically and are flagged for manual review instead.
  const safelist = ctx.tailwindConfig.safelist;
  if (safelist.length > 0) {
    sections.push(`/* Safelist ported from tailwind.config.ts (Tailwind v4 has no config safelist) */
@source inline("${safelist.join(" ")}");`);
  }
  for (const pattern of ctx.tailwindConfig.safelistPatterns) {
    ctx.manualReviewItems.push({
      file: "src/styles/app.css",
      reason: `tailwind.config.ts safelist pattern ${pattern} cannot be auto-converted — add the matching classes via @source inline("...") manually (Tailwind v4 has no regex safelist)`,
      severity: "warning",
    });
  }

  // Fix gotcha #43: `oklch(var(--x))` is emitted wherever a CMS theme var
  // looked like bare oklch coordinates, but if that var was actually
  // declared as hex elsewhere, the wrapper is invalid CSS (icons/fills
  // silently render black). Convert the hex declaration to an oklch triplet
  // so the wrapper stays valid.
  //
  // Scoped to the generator's OWN output (`sections` so far) — NOT the
  // site's original custom CSS appended below. That content should be
  // passed through as close to verbatim as possible; scanning/rewriting it
  // too would mean silently mutating a site-authored `--x: #hex;`
  // declaration if the site happens to also use the same
  // `oklch(var(--x))` idiom for an unrelated purpose.
  const generatorCss = sections.join("\n\n") + "\n";
  const { css: fixedGeneratorCss, fixed, flagged } = fixOklchHexMismatches(generatorCss);
  for (const note of fixed) {
    log(ctx, `[app.css] Fixed oklch/hex mismatch: ${note}`);
  }
  for (const note of flagged) {
    ctx.manualReviewItems.push({
      file: "src/styles/app.css",
      reason: note,
      severity: "warning",
    });
  }

  // ── Incorporate original site's custom CSS ────────────────────
  // Instead of throwing away the site's CSS, we extract and append
  // all custom rules (component overrides, @layer base, @font-face,
  // typography utilities, feature-specific CSS, etc.). Appended AFTER the
  // oklch fix above runs, so this content is never scanned/rewritten by it.
  const originalCss = findOriginalCss(ctx);
  let originalCssBlock = "";
  if (originalCss) {
    const customCss = extractCustomCss(originalCss);
    if (customCss) {
      let transformed = transformApplyDirectives(customCss);
      const cssFix = transformCss(transformed);
      transformed = cssFix.css;
      for (const note of cssFix.notes) {
        if (note.startsWith("MANUAL:")) {
          ctx.manualReviewItems.push({
            file: "src/styles/app.css",
            reason: note.replace(/^MANUAL:\s*/, ""),
            severity: "warning",
          });
        } else {
          log(ctx, `[app.css] ${note}`);
        }
      }
      originalCssBlock = `/* ═══════════════════════════════════════════════════════════════
   Original site CSS (migrated from tailwind.css)
   ═══════════════════════════════════════════════════════════════ */

${transformed}`;
    }
  }

  return originalCssBlock ? `${fixedGeneratorCss}\n${originalCssBlock}\n` : fixedGeneratorCss;
}
