interface ParsedHTMLTag {
  tag: string;
  type: "open" | "close" | "both";
  attributes?: Record<string, string | null>;
}

const OPEN_TAG_START_REGEX = /^<\s*([a-z-]+)\s*/;
const ATTRIBUTE_REGEX = /^([a-z-]+)(?:=("[^"]*"))?\s*/;
const END_OPEN_REGEX = /^>$/;
const END_OPEN_CLOSE_REGEX = /^\/>$/;
const CLOSE_TAG_REGEX = /^<\/\s*([a-z-]+)\s*>$/;

/**
 * Parses the opening or closing half of an HTML tag. To parse well-formed HTML, use DOMParser.parseFromString().
 */
export function parsePartialHTMLTag(text: string): ParsedHTMLTag {
  let partialText = text;
  const openTag = partialText.match(OPEN_TAG_START_REGEX);
  if (openTag) {
    const result: ParsedHTMLTag = {
      tag: openTag[1]!,
      type: "open",
    };
    partialText = partialText.slice(openTag[0].length);
    while (true) {
      const attribute = partialText.match(ATTRIBUTE_REGEX);
      if (!attribute) {
        break;
      }
      if (!result.attributes) {
        result.attributes = {};
      }
      result.attributes[attribute[1]!] = attribute[2] ? JSON.parse(attribute[2]) : null;
      partialText = partialText.slice(attribute[0].length);
    }
    if (partialText.match(END_OPEN_REGEX)) {
      return result;
    }
    if (partialText.match(END_OPEN_CLOSE_REGEX)) {
      result.type = "both";
      return result;
    }
  } else {
    const closeTag = partialText.match(CLOSE_TAG_REGEX);
    if (closeTag) {
      return {
        tag: closeTag[1]!,
        type: "close",
      };
    }
  }
  throw new Error(`Unable to parse partial HTML tag: ${text}`);
}
