import type { JSONContent } from "@tiptap/vue-3";

export function serializeToMarkdown(doc: JSONContent): string {
  if (doc.type !== "doc") return "";
  return serializeNodes(doc.content || []).replace(/\n{3,}/g, "\n\n").trimEnd();
}

function serializeNodes(nodes: JSONContent[], listIndent = 0): string {
  let out = "";
  for (let i = 0; i < nodes.length; i++) {
    out += serializeNode(nodes[i], listIndent);
  }
  return out;
}

function serializeNode(node: JSONContent, listIndent = 0): string {
  switch (node.type) {
    case "paragraph":
      return serializeInline(node.content || []) + "\n\n";

    case "heading": {
      const level = node.attrs?.level ?? 1;
      const prefix = "#".repeat(level) + " ";
      return prefix + serializeInline(node.content || []) + "\n\n";
    }

    case "bulletList":
      return (
        serializeBulletList(node.content || [], listIndent) +
        (listIndent === 0 ? "\n" : "")
      );

    case "orderedList":
      return (
        serializeOrderedList(node.content || [], listIndent, node.attrs?.start ?? 1) +
        (listIndent === 0 ? "\n" : "")
      );

    case "taskList":
      return (
        serializeTaskList(node.content || [], listIndent) +
        (listIndent === 0 ? "\n" : "")
      );

    case "listItem":
    case "taskItem":
      // Handled by their respective list serializers
      return "";

    case "codeBlock": {
      const lang = node.attrs?.language || "";
      const code = getTextContent(node);
      return "```" + lang + "\n" + code + "\n```\n\n";
    }

    case "blockquote": {
      const inner = serializeNodes(node.content || []);
      return (
        inner
          .trimEnd()
          .split("\n")
          .map((line) => "> " + line)
          .join("\n") + "\n\n"
      );
    }

    case "horizontalRule":
      return "---\n\n";

    case "hardBreak":
      return "\n";

    case "text":
      return applyMarks(node.text || "", node.marks || []);

    default:
      // Unknown node — try to serialize children
      if (node.content) {
        return serializeNodes(node.content);
      }
      return node.text || "";
  }
}

function serializeBulletList(
  items: JSONContent[],
  indent: number
): string {
  let out = "";
  for (const item of items) {
    if (item.type !== "listItem") continue;
    const children = item.content || [];
    const prefix = "  ".repeat(indent) + "- ";
    out += serializeListItem(children, prefix, indent);
  }
  return out;
}

function serializeOrderedList(
  items: JSONContent[],
  indent: number,
  start: number
): string {
  let out = "";
  let num = start;
  for (const item of items) {
    if (item.type !== "listItem") continue;
    const children = item.content || [];
    const prefix = "  ".repeat(indent) + num + ". ";
    out += serializeListItem(children, prefix, indent);
    num++;
  }
  return out;
}

function serializeTaskList(items: JSONContent[], indent: number): string {
  let out = "";
  for (const item of items) {
    if (item.type !== "taskItem") continue;
    const checked = item.attrs?.checked ? "x" : " ";
    const prefix = "  ".repeat(indent) + `- [${checked}] `;
    out += serializeListItem(item.content || [], prefix, indent);
  }
  return out;
}

function serializeListItem(
  children: JSONContent[],
  prefix: string,
  indent: number
): string {
  let out = "";
  for (let i = 0; i < children.length; i++) {
    const child = children[i];
    if (child.type === "paragraph") {
      if (i === 0) {
        out += prefix + serializeInline(child.content || []) + "\n";
      } else {
        out +=
          "  ".repeat(indent + 1) +
          serializeInline(child.content || []) +
          "\n";
      }
    } else if (child.type === "bulletList") {
      out += serializeBulletList(child.content || [], indent + 1);
    } else if (child.type === "orderedList") {
      out += serializeOrderedList(
        child.content || [],
        indent + 1,
        child.attrs?.start ?? 1
      );
    } else if (child.type === "taskList") {
      out += serializeTaskList(child.content || [], indent + 1);
    } else {
      out += serializeNode(child, indent + 1);
    }
  }
  return out;
}

function serializeInline(nodes: JSONContent[]): string {
  let out = "";
  for (const node of nodes) {
    if (node.type === "text") {
      out += applyMarks(node.text || "", node.marks || []);
    } else if (node.type === "hardBreak") {
      out += "\n";
    } else if (node.content) {
      out += serializeInline(node.content);
    }
  }
  return out;
}

function applyMarks(text: string, marks: NonNullable<JSONContent["marks"]>): string {
  let result = text;
  for (const mark of marks) {
    switch (mark.type) {
      case "bold":
        result = `**${result}**`;
        break;
      case "italic":
        result = `_${result}_`;
        break;
      case "strike":
        result = `~~${result}~~`;
        break;
      case "code":
        result = "`" + result + "`";
        break;
      case "link":
        result = `[${result}](${mark.attrs?.href || ""})`;
        break;
    }
  }
  return result;
}

function getTextContent(node: JSONContent): string {
  if (node.text) return node.text;
  if (!node.content) return "";
  return node.content.map(getTextContent).join("");
}
