/**
 * SCSS variables formatter.
 *
 * Output shape:
 *
 *   // tokens.scss
 *   $ds-color-primary: #4085F2;
 *   $ds-spacing-md: 16px;
 *
 *   // map for runtime mode access
 *   $ds-colors: (
 *     "primary": $ds-color-primary,
 *     ...
 *   );
 *
 * Modes: SCSS doesn't have CSS's runtime cascading, so multi-mode output
 * either emits one file per mode (splitByMode: true) or generates SCSS
 * maps keyed by mode name that consumers can `map-get` from.
 *
 * Composite tokens (typography, shadow) expand into multiple primitive
 * variables since SCSS variables hold a single value.
 */

import type { Token, TokenDocument, TokenSet, TokenValue } from "../types.js";
import type { FormatOptions, FormatResult } from "./index.js";

export function formatScss(
  doc: TokenDocument,
  opts: FormatOptions,
): FormatResult {
  const warnings: string[] = [];
  const files: FormatResult["files"] = [];

  const splitByMode = opts.target.splitByMode ?? false;
  const splitByCollection = opts.target.splitByCollection ?? false;
  const prefix = opts.target.prefix ?? "";

  if (splitByMode && splitByCollection) {
    for (const set of doc.sets) {
      for (const mode of set.modes) {
        files.push({
          path: filenameFor(opts, set, mode),
          content: renderSingleMode([set], mode, prefix, warnings),
        });
      }
    }
  } else if (splitByMode) {
    const allModes = new Set<string>();
    for (const set of doc.sets) for (const m of set.modes) allModes.add(m);
    for (const mode of allModes) {
      const sets = doc.sets.filter((s) => s.modes.includes(mode));
      files.push({
        path: filenameFor(opts, undefined, mode),
        content: renderSingleMode(sets, mode, prefix, warnings),
      });
    }
  } else if (splitByCollection) {
    for (const set of doc.sets) {
      files.push({
        path: filenameFor(opts, set),
        content: renderAllModes([set], prefix, warnings),
      });
    }
  } else {
    files.push({
      path: filenameFor(opts),
      content: renderAllModes(doc.sets, prefix, warnings),
    });
  }

  return { files, warnings };
}

function filenameFor(
  opts: FormatOptions,
  set?: TokenSet,
  mode?: string,
): string {
  if (opts.target.filename) return opts.target.filename;
  const parts: string[] = [];
  if (set) parts.push(slugify(set.name));
  if (mode) parts.push(slugify(mode));
  if (parts.length === 0) parts.push("tokens");
  return `_${parts.join(".")}.scss`;
}

function slugify(s: string): string {
  return s
    .trim()
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, "-")
    .replace(/^-+|-+$/g, "");
}

function varName(path: string[], prefix: string): string {
  return `$${prefix}${path.map(slugify).join("-")}`;
}

function renderSingleMode(
  sets: TokenSet[],
  mode: string,
  prefix: string,
  warnings: string[],
): string {
  const lines: string[] = [];
  lines.push("// Generated by figma-console-mcp — do not edit by hand");
  lines.push(`// Mode: ${mode}`);
  lines.push("");
  for (const set of sets) {
    if (!set.modes.includes(mode)) continue;
    lines.push(`// — ${set.name} —`);
    for (const token of set.tokens) {
      const value = token.values[mode];
      if (!value) continue;
      emitSassLines(token, value, prefix, lines, warnings);
    }
    lines.push("");
  }
  return lines.join("\n");
}

function renderAllModes(
  sets: TokenSet[],
  prefix: string,
  warnings: string[],
): string {
  const lines: string[] = [];
  lines.push("// Generated by figma-console-mcp — do not edit by hand");
  lines.push("");

  // For multi-mode sets, emit a primary variable + a mode-keyed map.
  // Single-mode sets emit straight variables.
  for (const set of sets) {
    lines.push(`// — ${set.name} —`);
    const isMultiMode = set.modes.length > 1;
    const primaryMode = pickPrimaryMode(set.modes);

    for (const token of set.tokens) {
      // Primary value as the bare variable.
      const primary = token.values[primaryMode];
      if (primary) emitSassLines(token, primary, prefix, lines, warnings);

      // Other modes as a map: $ds-color-primary--modes: ("Dark": #..., "Vibrant": #...)
      if (isMultiMode) {
        const otherModes = set.modes.filter((m) => m !== primaryMode);
        const entries: string[] = [];
        for (const mode of otherModes) {
          const v = token.values[mode];
          if (!v) continue;
          const formatted = scssValueFor(v, token, prefix, warnings);
          if (formatted !== null) {
            entries.push(`  "${mode}": ${formatted}`);
          }
        }
        if (entries.length > 0) {
          const mapName = `${varName(token.path, prefix)}--modes`;
          lines.push(`${mapName}: (`);
          lines.push(entries.join(",\n"));
          lines.push(`);`);
        }
      }
    }
    lines.push("");
  }
  return lines.join("\n");
}

function pickPrimaryMode(modes: string[]): string {
  return modes.find((m) => /^(default|light|value)$/i.test(m)) ?? modes[0];
}

function emitSassLines(
  token: Token,
  value: TokenValue,
  prefix: string,
  out: string[],
  warnings: string[],
): void {
  const sassName = varName(token.path, prefix);

  if (value.reference) {
    const bareRef = value.reference.replace(/^\{|\}$/g, "");
    const libMatch = bareRef.match(/^__library:(.+)$/);
    if (libMatch || bareRef === "unknown") {
      const originalId = libMatch ? libMatch[1] : "unknown";
      warnings.push(
        `Skipped ${token.path.join(".")} in SCSS — cross-library variable ${originalId}.`,
      );
      out.push(
        `// ${sassName}: skipped — cross-library alias to ${originalId}`,
      );
      return;
    }
    const refPath = bareRef.split(".");
    out.push(`${sassName}: ${varName(refPath, prefix)};`);
    return;
  }

  if (value.literal === undefined || value.literal === null) {
    warnings.push(`Token ${token.path.join(".")} has no value.`);
    return;
  }

  if (token.type === "typography" && typeof value.literal === "object") {
    const t = value.literal as Record<string, unknown>;
    for (const sub of ["fontFamily", "fontSize", "fontWeight", "lineHeight", "letterSpacing"]) {
      if (t[sub] !== undefined) {
        out.push(
          `${sassName}-${kebab(sub)}: ${formatScssLiteral(t[sub], token.type)};`,
        );
      }
    }
    return;
  }
  if (token.type === "shadow" && typeof value.literal === "object") {
    const css = renderShadow(value.literal);
    if (css) out.push(`${sassName}: ${css};`);
    return;
  }

  out.push(`${sassName}: ${formatScssLiteral(value.literal, token.type)};`);
}

function scssValueFor(
  value: TokenValue,
  token: Token,
  prefix: string,
  _warnings: string[],
): string | null {
  if (value.reference) {
    const bareRef = value.reference.replace(/^\{|\}$/g, "");
    if (bareRef.startsWith("__library:") || bareRef === "unknown") return null;
    return varName(bareRef.split("."), prefix);
  }
  if (value.literal === undefined || value.literal === null) return null;
  return formatScssLiteral(value.literal, token.type);
}

function kebab(s: string): string {
  return s.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
}

function formatScssLiteral(value: unknown, type: string): string {
  if (typeof value === "number") {
    if (type === "dimension") return `${value}px`;
    return String(value);
  }
  if (typeof value === "string") {
    if (type === "color") return value;
    if (type === "fontFamily" || type === "string") {
      return needsQuoting(value) ? JSON.stringify(value) : value;
    }
    return value;
  }
  if (typeof value === "boolean") return String(value);
  return JSON.stringify(value);
}

function needsQuoting(s: string): boolean {
  if (/^["']/.test(s)) return false;
  if (/^[\d.]+([a-z%]+)?$/.test(s)) return false;
  if (/^[a-zA-Z_][a-zA-Z0-9_-]*$/.test(s)) return false;
  return true;
}

function renderShadow(shadow: unknown): string | null {
  if (Array.isArray(shadow)) {
    return shadow.map(renderShadow).filter(Boolean).join(", ");
  }
  if (!shadow || typeof shadow !== "object") return null;
  const s = shadow as Record<string, unknown>;
  const inset = s.inset ? "inset " : "";
  const x = withPx(s.offsetX);
  const y = withPx(s.offsetY);
  const blur = withPx(s.blur);
  const spread = s.spread !== undefined ? ` ${withPx(s.spread)}` : "";
  const color = s.color;
  if (!x || !y || !blur || typeof color !== "string") return null;
  return `${inset}${x} ${y} ${blur}${spread} ${color}`;
}

function withPx(v: unknown): string {
  if (typeof v === "number") return `${v}px`;
  if (typeof v === "string") return v;
  return "";
}
