/**
 * @wrnexus/styles — global stylesheet pipeline + app config.
 *
 * Works for SSR and CSR: the bundled stylesheet is `<link>`ed into every page's
 * `<head>`, so it styles server-rendered markup and hydrated client islands
 * alike. Use any CSS framework via `@import` in global.css (npm) or via a CDN
 * link in `wrnexus.config.ts`'s `head` field.
 */

export type {
  AppConfig,
  BuildConfig,
  ConfigIssue,
  DevToolbarConfig,
  ExplainedConfig,
  ExperimentalConfig,
  MobileConfig,
  NavigationConfig,
  ObservabilityConfig,
  PerformanceConfig,
  TenancyConfig,
  PwaConfig,
  StylesConfig,
  StyleProcessContext,
  Mode,
  ResolvedConfigLayers,
} from "./config.ts";
export {
  defineConfig,
  explainAppConfig,
  headToString,
  loadAppConfig,
  loadEnv,
  loadRawConfig,
  resolveProfile,
  resolveConfigLayers,
  validateAppConfig,
} from "./config.ts";
export {
  CURRENT_COMPATIBILITY_DATE,
  CURRENT_FRAMEWORK_BEHAVIOUR,
  isCompatibilityDate,
  resolveCompatibility,
} from "./compatibility.ts";
export type { CompatibilityPolicy, CompatibilityReport } from "./compatibility.ts";
export { findStyleEntry, bundleCss } from "./styles.ts";
export type { FontConfig, GoogleFont, LocalFontFace, FontDisplay } from "./fonts.ts";
export { renderFontHead, renderProductionFontHead, fontCspSources } from "./fonts.ts";
export type {
  ThemeConfig,
  ThemeTokens,
  ResolvedTheme,
  ThemePaletteName,
  CustomThemePalette,
  ThemeToken,
  ThemeSemanticColor,
} from "./theme.ts";
export {
  DEFAULT_THEMES,
  THEME_PALETTES,
  THEME_PALETTE_NAMES,
  THEME_COOKIE,
  THEME_CSS_HREF,
  THEME_CSS_PREFIX,
  THEME_JS_HREF,
  activeThemeCssHref,
  resolveThemeConfig,
  resolveThemeName,
  renderThemeCss,
  renderActiveThemeCss,
  renderThemeRuntime,
  defineThemeTokens,
  themeVar,
} from "./theme.ts";

import type { Mode, StyleProcessContext, StylesConfig } from "./config.ts";
import { bundleCss } from "./styles.ts";
import { mkdir, writeFile } from "node:fs/promises";
import { isAbsolute, join, relative } from "node:path";

/**
 * Produce the final CSS for an entry: run the config's custom processor if one
 * is provided (Tailwind/PostCSS/Sass), otherwise use the built-in Bun bundler.
 *
 * Development can fall back to best-effort CSS. Production throws by default so
 * a deployment cannot silently ship unprocessed Tailwind/PostCSS directives.
 */
export async function renderStyles(
  ctx: StyleProcessContext,
  styles?: StylesConfig,
): Promise<string> {
  if (!ctx.entryPath && !ctx.entries?.length) return "";
  const processContext =
    styles?.includePackageSources === false ? ctx : await packageAwareStyleContext(ctx);
  if (!processContext.entryPath) return "";
  if (styles?.process) {
    try {
      return String(await styles.process(processContext));
    } catch (err) {
      const msg = err instanceof Error ? err.message : String(err);
      const details =
        `[wrnexus] Stylesheet processing failed.\n` +
        `  ${msg.split("\n")[0]}\n` +
        "  If you use Tailwind, run `bun install` in the application workspace so the CLI and plugins are available.";
      const failureMode = styles.failureMode ?? (ctx.mode === "production" ? "throw" : "fallback");
      if (failureMode === "throw") throw new Error(details, { cause: err });
      console.error(`\n${details}\n  Serving best-effort unprocessed CSS in development.\n`);
      return await fallbackCss(processContext);
    }
  }
  return bundleCss(processContext.entryPath!, processContext.mode);
}

/**
 * Best-effort CSS when a custom processor fails: try the built-in bundler; if that
 * also fails (e.g. `@import "tailwindcss"` can't resolve), serve the raw entry with
 * the tool-only directives stripped so the page still renders.
 */
async function fallbackCss(ctx: StyleProcessContext): Promise<string> {
  try {
    return await bundleCss(ctx.entryPath!, ctx.mode);
  } catch {
    try {
      const raw = await Bun.file(ctx.entryPath!).text();
      return raw.replace(/@import\s+["']tailwindcss["'];?/g, "").replace(/@source[^;\n]*;?/g, "");
    } catch {
      return "";
    }
  }
}

async function packageAwareStyleContext(ctx: StyleProcessContext): Promise<StyleProcessContext> {
  const sources = [...new Set((ctx.sources ?? []).filter(Boolean))];
  const entries = [...new Set((ctx.entries ?? []).filter(Boolean))];
  if (sources.length === 0 && entries.length === 0) return ctx;
  if (!ctx.entryPath && entries.length === 0) return ctx;
  const outputDir = join(ctx.appRoot, ".wrnexus", "styles");
  const wrapper = join(outputDir, "global.with-package-sources.css");
  await mkdir(outputDir, { recursive: true });
  const localPath = (value: string): string => {
    const path = isAbsolute(value) ? relative(outputDir, value) : value;
    const normalized = path.replace(/\\/g, "/");
    return normalized.startsWith(".") ? normalized : `./${normalized}`;
  };
  const quote = (value: string) => localPath(value).replace(/"/g, '\\"');
  const imports = [...(ctx.entryPath ? [ctx.entryPath] : []), ...entries]
    .map((value) => `@import "${quote(value)}";`)
    .join("\n");
  const directives = sources.map((value) => `@source "${quote(value)}";`).join("\n");
  await writeFile(
    wrapper,
    `${imports}\n\n/* WRNexus package scan sources */\n${directives}\n`,
    "utf8",
  );
  return {
    ...ctx,
    originalEntryPath: ctx.entryPath,
    entryPath: wrapper,
    sources,
    entries,
  };
}

export type { Mode as StylesMode };

export {
  auditWireTokens,
  normalizeStyleSources,
  tailwindSourceDirectives,
  contrast,
  auditCssPerformance,
} from "./audit.ts";
export type {
  CssTokenAudit,
  StyleSource,
  ContrastResult,
  CssPerformanceAuditIssue,
} from "./audit.ts";
export * from "./theme.ts";
