import type { TipTapJSON } from "@/lib/drive/richtext-import"
import type { DocsTableBorder } from "@/lib/drive/docs-table-types"

function isRecord(value: unknown): value is Record<string, unknown> {
  return Boolean(value) && typeof value === "object" && !Array.isArray(value)
}

function parseHtmlBorder(style: string, side: "top" | "right" | "bottom" | "left"): DocsTableBorder | null {
  const match = style.match(new RegExp(`border-${side}:\\s*([^;]+)`, "i"))
  if (!match) return null
  const value = match[1]?.trim()
  if (!value || value === "none" || value === "0") return null
  const parts = value.split(/\s+/)
  if (parts.length < 2) return null
  const size = parseFloat(parts[0] ?? "")
  const styleName = parts[1] ?? "solid"
  const color = parts.slice(2).join(" ") || "#000000"
  return {
    size: Number.isFinite(size) ? Math.round(size * 8) : 6,
    style: styleName === "solid" ? "single" : styleName,
    color,
  }
}

function parseHtmlCellAttrs(cell: HTMLTableCellElement): Record<string, unknown> {
  const attrs: Record<string, unknown> = {}

  const colspan = Number(cell.getAttribute("colspan") || "1")
  const rowspan = Number(cell.getAttribute("rowspan") || "1")
  if (colspan > 1) attrs.colspan = colspan
  if (rowspan > 1) attrs.rowspan = rowspan

  const width = cell.getAttribute("width") || cell.style.width
  if (width) {
    const px = width.endsWith("px") ? parseFloat(width) : parseFloat(width)
    if (Number.isFinite(px) && px > 0) attrs.colwidth = [Math.round(px)]
  }

  const bg =
    cell.getAttribute("bgcolor") ||
    cell.getAttribute("data-background-color") ||
    cell.style.backgroundColor
  if (bg) attrs.backgroundColor = bg

  const align = cell.getAttribute("align") || cell.style.textAlign
  if (align === "left" || align === "center" || align === "right" || align === "justify") {
    attrs.align = align
  }

  const valign = cell.getAttribute("valign") || cell.style.verticalAlign
  if (valign === "top" || valign === "middle" || valign === "bottom") {
    attrs.verticalAlign = valign
  }

  const inlineStyle = cell.getAttribute("style") ?? ""
  const borderTop = parseHtmlBorder(inlineStyle, "top")
  const borderRight = parseHtmlBorder(inlineStyle, "right")
  const borderBottom = parseHtmlBorder(inlineStyle, "bottom")
  const borderLeft = parseHtmlBorder(inlineStyle, "left")
  if (borderTop) attrs.borderTop = borderTop
  if (borderRight) attrs.borderRight = borderRight
  if (borderBottom) attrs.borderBottom = borderBottom
  if (borderLeft) attrs.borderLeft = borderLeft

  return attrs
}

function inlineTextFromElement(el: HTMLElement): TipTapJSON[] {
  const content: TipTapJSON[] = []
  for (const node of el.childNodes) {
    if (node.nodeType === Node.TEXT_NODE) {
      const text = node.textContent ?? ""
      if (text) content.push({ type: "text", text })
      continue
    }
    if (node.nodeType !== Node.ELEMENT_NODE) continue
    const child = node as HTMLElement
    const tag = child.tagName.toLowerCase()
    const marks: TipTapJSON[] = []
    if (tag === "strong" || tag === "b") marks.push({ type: "bold" })
    if (tag === "em" || tag === "i") marks.push({ type: "italic" })
    if (tag === "u") marks.push({ type: "underline" })
    const text = child.textContent ?? ""
    if (text) content.push({ type: "text", text, ...(marks.length ? { marks } : {}) })
  }
  return content
}

export function htmlTableToTipTap(table: HTMLTableElement): TipTapJSON {
  const rows = Array.from(table.querySelectorAll(":scope > tbody > tr, :scope > tr"))
  const tableAttrs: Record<string, unknown> = {}
  const align = table.getAttribute("align") || table.style.margin
  if (table.getAttribute("align") === "center") tableAttrs.alignment = "center"
  else if (table.getAttribute("align") === "right") tableAttrs.alignment = "right"

  const width = table.getAttribute("width") || table.style.width
  if (width && width !== "auto") tableAttrs.layout = "fixed"

  const rowNodes = rows.map((row, rowIndex) => {
    const htmlRow = row as HTMLTableRowElement
    const cells = Array.from(htmlRow.querySelectorAll(":scope > th, :scope > td"))
    const rowAttrs: Record<string, unknown> = {}
    const rowHeight = htmlRow.style.height || htmlRow.getAttribute("height")
    if (rowHeight) rowAttrs.rowHeight = rowHeight.endsWith("px") ? rowHeight : `${rowHeight}px`
    if (rowIndex === 0 && cells.every((cell) => cell.tagName.toLowerCase() === "th")) {
      rowAttrs.header = true
    }

    return {
      type: "tableRow",
      ...(Object.keys(rowAttrs).length ? { attrs: rowAttrs } : {}),
      content: cells.map((cell) => {
        const htmlCell = cell as HTMLTableCellElement
        const isHeader = htmlCell.tagName.toLowerCase() === "th"
        const attrs = parseHtmlCellAttrs(htmlCell)
        const paragraphContent = inlineTextFromElement(htmlCell)
        return {
          type: isHeader ? "tableHeader" : "tableCell",
          ...(Object.keys(attrs).length ? { attrs } : {}),
          content: [
            {
              type: "paragraph",
              content: paragraphContent.length ? paragraphContent : [],
            },
          ],
        }
      }),
    }
  })

  return {
    type: "table",
    ...(Object.keys(tableAttrs).length ? { attrs: tableAttrs } : {}),
    content: rowNodes.length ? rowNodes : [{ type: "tableRow", content: [{ type: "tableCell", content: [{ type: "paragraph" }] }] }],
  }
}

function normalizeCellNode(node: TipTapJSON): TipTapJSON | null {
  if (!isRecord(node)) return null
  if (node.type !== "tableCell" && node.type !== "tableHeader") return node

  const attrs = isRecord(node.attrs) ? { ...node.attrs } : {}
  if (attrs.rowspan === 0) return null

  if (typeof attrs.colspan !== "number" || attrs.colspan < 1) attrs.colspan = 1
  if (typeof attrs.rowspan !== "number" || attrs.rowspan < 1) attrs.rowspan = 1

  if (Array.isArray(attrs.colwidth)) {
    const colwidth = (attrs.colwidth as unknown[])
      .map((value) => (typeof value === "number" && Number.isFinite(value) ? value : null))
      .filter((value): value is number => value != null)
    if (colwidth.length) attrs.colwidth = colwidth
    else delete attrs.colwidth
  }

  const content = Array.isArray(node.content)
    ? node.content.map((child) => normalizeTableTree(child)).filter(Boolean)
    : [{ type: "paragraph" }]

  return {
    ...node,
    attrs,
    content: content.length ? content : [{ type: "paragraph" }],
  }
}

function normalizeRowNode(node: TipTapJSON): TipTapJSON | null {
  if (!isRecord(node) || node.type !== "tableRow") return node
  const content = Array.isArray(node.content)
    ? node.content
        .map((child) => normalizeCellNode(child))
        .filter((child): child is TipTapJSON => Boolean(child))
    : []

  if (!content.length) return null
  return { ...node, content }
}

function normalizeTableNode(node: TipTapJSON): TipTapJSON | null {
  if (!isRecord(node) || node.type !== "table") return node
  const content = Array.isArray(node.content)
    ? node.content
        .map((child) => normalizeRowNode(child))
        .filter((child): child is TipTapJSON => Boolean(child))
    : []

  if (!content.length) {
    return {
      type: "table",
      content: [
        {
          type: "tableRow",
          content: [{ type: "tableCell", content: [{ type: "paragraph" }] }],
        },
      ],
    }
  }

  return { ...node, content }
}

function normalizeTableTree(node: unknown): TipTapJSON | null {
  if (!isRecord(node)) return null
  if (node.type === "table") return normalizeTableNode(node)
  if (node.type === "tableRow") return normalizeRowNode(node)
  if (node.type === "tableCell" || node.type === "tableHeader") return normalizeCellNode(node)

  if (Array.isArray(node.content)) {
    return {
      ...node,
      content: node.content.map((child) => normalizeTableTree(child)).filter(Boolean),
    }
  }

  return node
}

/** Preserve DOCX/HTML table attrs and drop invalid merged-cell placeholders. */
export function normalizeImportedTables(content: TipTapJSON): TipTapJSON {
  const normalized = normalizeTableTree(content)
  return (normalized ?? content) as TipTapJSON
}
