// html-to-wx-nodes.ts
declare global {
  interface DOMParser {
    new (): DOMParser;
    parseFromString(source: string, mimeType: string): Document;
  }

  interface Document {
    body: HTMLElement;
  }

  interface HTMLElement extends Element {
    innerHTML: string;
  }

  interface Element {
    tagName: string;
    attributes: NamedNodeMap;
    childNodes: ChildNode[];
  }

  interface ChildNode {
    nodeType: number;
    textContent: string | null;
  }

  interface NamedNodeMap {
    length: number;
    item(index: number): Attr | null;
  }

  interface Attr {
    name: string;
    value: string;
  }

  const Node: {
    TEXT_NODE: number;
    ELEMENT_NODE: number;
  };

  var DOMParser: {
    prototype: DOMParser;
    new (): DOMParser;
  };
}

type WxNode = {
  name?: string; // 元素节点：标签名，如 'div','img'
  attrs?: Record<string, string>; // 属性集合
  children?: WxNode[]; // 子节点（元素或文本）
  type?: "text"; // 文本节点标记
  text?: string; // 文本节点内容
};

function decodeHtmlEntities(str: string) {
  return str.replace(/&lt;|&gt;|&amp;|&quot;|&#39;/g, (s) => {
    switch (s) {
      case "&lt;":
        return "<";
      case "&gt;":
        return ">";
      case "&amp;":
        return "&";
      case "&quot;":
        return '"';
      case "&#39;":
        return "'";
      default:
        return s;
    }
  });
}

function elementToNode(el: Element): WxNode {
  const node: WxNode = {
    name: el.tagName.toLowerCase(),
    attrs: {},
    children: [],
  };

  // copy attributes
  for (let i = 0; i < el.attributes.length; i++) {
    const a = el.attributes.item(i)!;
    node.attrs![a.name] = a.value;
  }

  // map special: if data-align present, try to map to style (optional)
  if (node.attrs!["data-align"] && !node.attrs!["style"]) {
    const align = node.attrs!["data-align"];
    if (node.name === "img") {
      if (align === "center") {
        node.attrs!["style"] = "display:block;margin:0 auto;";
      } else if (align === "left") {
        node.attrs!["style"] = "float:left;margin-right:8px;";
      } else if (align === "right") {
        node.attrs!["style"] = "float:right;margin-left:8px;";
      }
    } else {
      // for block elements like div, p
      node.attrs!["style"] = `text-align:${align};`;
    }
  }

  // children: process childNodes
  el.childNodes.forEach((child: ChildNode) => {
    if (child.nodeType === Node.TEXT_NODE) {
      const txt = (child.textContent || "").trim();
      if (txt.length > 0) {
        node.children!.push({ type: "text", text: decodeHtmlEntities(txt) });
      }
    } else if (child.nodeType === Node.ELEMENT_NODE) {
      node.children!.push(elementToNode(child as Element & ChildNode));
    }
  });

  // if no children for img, remove children prop (not required)
  if (node.name === "img") {
    delete node.children;
  }

  return node;
}

export function htmlToWxNodes(html: string): WxNode[] {
  html = (html || "").trim();
  if (!html) return [];

  // try DOMParser (browser / devtools)
  if (typeof DOMParser !== "undefined") {
    try {
      const parser = new DOMParser();
      const doc = parser.parseFromString(html, "text/html");

      const body = doc.body;
      const nodes: WxNode[] = [];
      body.childNodes.forEach((child) => {
        if (child.nodeType === Node.TEXT_NODE) {
          const txt = (child.textContent || "").trim();
          if (txt) nodes.push({ type: "text", text: decodeHtmlEntities(txt) });
        } else if (child.nodeType === Node.ELEMENT_NODE) {
          nodes.push(elementToNode(child as Element & ChildNode));
        }
      });
      return nodes;
    } catch (e) {
      // fallthrough to fallback parser
      console.warn("DOMParser failed, fallback to simple parse", e);
    }
  }

  // Fallback: very small parser for simple HTML (img, div, p, plain text)
  const fallbackNodes: WxNode[] = [];
  const imgRegex = /<img\b([^>]+?)\/?>/gi;
  let lastIndex = 0;
  let m;
  while ((m = imgRegex.exec(html)) !== null) {
    // push text before this img as text node
    if (m.index > lastIndex) {
      const txt = html
        .slice(lastIndex, m.index)
        .replace(/<[^>]+>/g, "")
        .trim();
      if (txt)
        fallbackNodes.push({ type: "text", text: decodeHtmlEntities(txt) });
    }
    const attrsStr = m[1];
    const attrs: Record<string, string> = {};
    attrsStr.replace(
      /([^\s=]+)(?:="([^"]*)")?/g,
      (_: string, k: string, v: string) => {
        attrs[k] = v || "";
        return "";
      }
    );
    // normalize src, alt
    const node: WxNode = {
      name: "img",
      attrs: {
        src: attrs.src || attrs["data-src"] || "",
        alt: attrs.alt || "",
      },
    };
    if (attrs["data-align"]) {
      const al = attrs["data-align"];
      if (al === "center")
        node.attrs!["style"] =
          "display:block;margin:0 auto;max-width:100%;height:auto;";
      if (al === "left")
        node.attrs!["style"] =
          "float:left;margin-right:8px;max-width:100%;height:auto;";
      if (al === "right")
        node.attrs!["style"] =
          "float:right;margin-left:8px;max-width:100%;height:auto;";
    }
    fallbackNodes.push(node);
    lastIndex = imgRegex.lastIndex;
  }
  // trailing text
  if (lastIndex < html.length) {
    const trailing = html
      .slice(lastIndex)
      .replace(/<[^>]+>/g, "")
      .trim();
    if (trailing)
      fallbackNodes.push({ type: "text", text: decodeHtmlEntities(trailing) });
  }
  return fallbackNodes;
}
