import { ELEMENT_NODE, parse, TEXT_NODE } from "ultrahtml";
import { querySelector } from "ultrahtml/selector";
import { decodeHtml, htmlDecodeQuotes } from "../../util/encoding.js";
import { fetchIsland } from "../../util/kit.js";
import { logger } from "../../util/logger.js";
import { splitCssDeclarations } from "../utils/css.js";
import { walkTree } from "./plugins.js";
import encoding from "./plugins/encoding.js";
import imageSrc from "./plugins/imageSrc.js";
import styleDirectives from "./plugins/styleDirectives.js";
import { applyEmojis } from "./transforms/emojis/index.js";
const RE_KEBAB_SEGMENT = /-([a-z])/g;
const RE_AMP_ENTITY = /&amp;/g;
const RE_CSS_QUOTES = /^['"](.+)['"]$/;
const RE_IMPORTANT = /\s*!important\s*$/;
const RE_BODY_CONTENT = /<body>([\s\S]*)<\/body>/;
function camelCase(str) {
  return str.replace(RE_KEBAB_SEGMENT, (_, c) => c.toUpperCase());
}
const SVG_CAMEL_ATTRS = {
  viewbox: "viewBox",
  preserveaspectratio: "preserveAspectRatio",
  basefrequency: "baseFrequency",
  baseprofile: "baseProfile",
  clippathunits: "clipPathUnits",
  diffuseconstant: "diffuseConstant",
  edgemode: "edgeMode",
  filterunits: "filterUnits",
  glyphref: "glyphRef",
  gradienttransform: "gradientTransform",
  gradientunits: "gradientUnits",
  kernelmatrix: "kernelMatrix",
  kernelunitlength: "kernelUnitLength",
  keypoints: "keyPoints",
  keysplines: "keySplines",
  keytimes: "keyTimes",
  lengthadjust: "lengthAdjust",
  limitingconeangle: "limitingConeAngle",
  markerheight: "markerHeight",
  markerunits: "markerUnits",
  markerwidth: "markerWidth",
  maskcontentunits: "maskContentUnits",
  maskunits: "maskUnits",
  numoctaves: "numOctaves",
  pathlength: "pathLength",
  patterncontentunits: "patternContentUnits",
  patterntransform: "patternTransform",
  patternunits: "patternUnits",
  pointsatx: "pointsAtX",
  pointsaty: "pointsAtY",
  pointsatz: "pointsAtZ",
  repeatcount: "repeatCount",
  repeatdur: "repeatDur",
  requiredextensions: "requiredExtensions",
  specularconstant: "specularConstant",
  specularexponent: "specularExponent",
  spreadmethod: "spreadMethod",
  startoffset: "startOffset",
  stddeviation: "stdDeviation",
  stitchtiles: "stitchTiles",
  surfacescale: "surfaceScale",
  systemlanguage: "systemLanguage",
  tablevalues: "tableValues",
  targetx: "targetX",
  targety: "targetY",
  textlength: "textLength",
  xchannelselector: "xChannelSelector",
  ychannelselector: "yChannelSelector",
  zoomandpan: "zoomAndPan"
};
export const SVG_CAMEL_ATTR_VALUES = new Set(Object.values(SVG_CAMEL_ATTRS));
const RE_PERCENT = /^\d+%$/;
const RE_VIEWBOX_SEP = /[\s,]+/;
export function resolveSvgDimension(props, style, key) {
  const attr = props[key];
  if (attr != null && !RE_PERCENT.test(String(attr))) {
    const n = Number(attr);
    if (!Number.isNaN(n))
      return n;
  }
  const sv = style?.[key];
  if (sv != null) {
    const n = typeof sv === "number" ? sv : Number.parseInt(String(sv));
    if (!Number.isNaN(n))
      return n;
  }
  const vb = props.viewBox || props.viewbox;
  if (typeof vb === "string") {
    const parts = vb.split(RE_VIEWBOX_SEP);
    if (parts.length === 4) {
      const n = Number(parts[key === "width" ? 2 : 3]);
      if (!Number.isNaN(n))
        return n;
    }
  }
}
export function parseStyleAttr(style) {
  if (!style)
    return void 0;
  const result = {};
  for (const decl of splitCssDeclarations(style.replace(RE_AMP_ENTITY, "&"))) {
    const colonIdx = decl.indexOf(":");
    if (colonIdx === -1)
      continue;
    const prop = decl.slice(0, colonIdx).trim();
    const val = decl.slice(colonIdx + 1).trim();
    if (prop && val) {
      const cleanVal = val.replace(RE_IMPORTANT, "");
      result[camelCase(prop)] = prop === "font-family" ? cleanVal.replace(RE_CSS_QUOTES, "$1") : cleanVal;
    }
  }
  return Object.keys(result).length ? result : void 0;
}
export function elementToVNode(el) {
  const props = {};
  const { style, ...attrs } = el.attributes;
  const parsedStyle = parseStyleAttr(style);
  if (parsedStyle)
    props.style = parsedStyle;
  for (const [name, value] of Object.entries(attrs)) {
    const key = SVG_CAMEL_ATTRS[name] || name;
    props[key] = typeof value === "string" ? decodeHtml(value) : value;
  }
  const children = [];
  for (const child of el.children) {
    if (child.type === ELEMENT_NODE) {
      children.push(elementToVNode(child));
    } else if (child.type === TEXT_NODE) {
      const text = child.value;
      if (text.trim())
        children.push(text);
    }
  }
  if (children.length)
    props.children = children;
  return { type: el.name, props };
}
export function htmlToVNode(html) {
  const doc = parse(html);
  const root = querySelector(doc, "div");
  if (!root)
    throw new Error("Failed to parse HTML - no root div found");
  return elementToVNode(root);
}
const UNSUPPORTED_SVG_ELEMENTS = /* @__PURE__ */ new Set(["text", "tspan", "textPath", "foreignObject", "switch", "a"]);
export function warnUnsupportedSvgElements(vnode, component) {
  const unsupported = /* @__PURE__ */ new Set();
  const collectUnsupported = (node) => {
    if (UNSUPPORTED_SVG_ELEMENTS.has(node.type))
      unsupported.add(`<${node.type}>`);
    if (Array.isArray(node.props?.children)) {
      for (const child of node.props.children) {
        if (child && typeof child === "object")
          collectUnsupported(child);
      }
    }
  };
  const walk = (node) => {
    if (node.type === "svg") {
      collectUnsupported(node);
    } else if (Array.isArray(node.props?.children)) {
      for (const child of node.props.children) {
        if (child && typeof child === "object")
          walk(child);
      }
    }
  };
  walk(vnode);
  if (unsupported.size) {
    const elements = [...unsupported].join(", ");
    const source = component ? ` in "${component}"` : "";
    logger.warn(`SVG ${elements} elements${source} are not supported by image renderers and will not be displayed. Convert text to <path> elements instead.`);
  }
}
export async function createVNodes(ctx, options) {
  let html = ctx.options.html;
  if (!html) {
    const island = await fetchIsland(ctx.e, ctx.options.component, typeof ctx.options.props !== "undefined" ? ctx.options.props : ctx.options);
    island.html = htmlDecodeQuotes(island.html);
    await applyEmojis(ctx, island);
    html = island.html;
    if (html?.includes("<body>")) {
      html = html.match(RE_BODY_CONTENT)?.[1] || "";
    }
  }
  const baseStyle = `position: relative; display: flex; margin: 0 auto; width: ${ctx.options.width}px; height: ${ctx.options.height}px; overflow: hidden;`;
  const fullStyle = options?.wrapperStyle ? `${baseStyle} ${options.wrapperStyle}` : baseStyle;
  const template = `<div style="${fullStyle}">${html}</div>`;
  const vnodeTree = htmlToVNode(template);
  const rootChild = vnodeTree.props.children?.[0];
  if (rootChild && typeof rootChild === "object") {
    rootChild.props.style = { width: "100%", height: "100%", ...rootChild.props.style };
  }
  warnUnsupportedSvgElements(vnodeTree, ctx.options.component);
  await Promise.all(walkTree(ctx, vnodeTree, [encoding, styleDirectives, imageSrc]));
  return vnodeTree;
}
