import type { Token } from "markdown-it";
import {
  type Node as ProsemirrorNode,
  type NodeSpec,
  Slice,
} from "prosemirror-model";
import type { EditorState } from "prosemirror-state";
import { Plugin, PluginKey } from "prosemirror-state";
import { Decoration, DecorationSet } from "prosemirror-view";
import { TableMap } from "prosemirror-tables";
import { getCellAttrs, setCellAttrs } from "../lib/table";
import Node from "./Node";

export default class TableCell extends Node {
  get name() {
    return "td";
  }

  get schema(): NodeSpec {
    return {
      content: "block+",
      tableRole: "cell",
      group: "cell",
      isolating: true,
      parseDOM: [{ tag: "td", getAttrs: getCellAttrs }],
      toDOM(node) {
        return ["td", setCellAttrs(node), 0];
      },
      attrs: {
        colspan: { default: 1 },
        rowspan: { default: 1 },
        alignment: { default: null },
        colwidth: { default: null },
      },
    };
  }

  toMarkdown() {
    // see: renderTable
  }

  parseMarkdown() {
    return {
      block: "td",
      getAttrs: (tok: Token) => ({ alignment: tok.info }),
    };
  }

  get plugins() {
    const createCellDecorations = (state: EditorState) => {
      const { doc } = state;
      const decorations: Decoration[] = [];

      // Iterate through all tables in the document
      doc.descendants((node: ProsemirrorNode, pos: number) => {
        if (node.type.spec.tableRole === "table") {
          const map = TableMap.get(node);

          // Mark cells in the first column and last row of this table
          node.descendants((cellNode: ProsemirrorNode, cellPos: number) => {
            if (
              cellNode.type.spec.tableRole === "cell" ||
              cellNode.type.spec.tableRole === "header_cell"
            ) {
              const cellOffset = cellPos;
              const cellIndex = map.map.indexOf(cellOffset);

              if (cellIndex !== -1) {
                const col = cellIndex % map.width;
                const row = Math.floor(cellIndex / map.width);
                const rowspan = cellNode.attrs.rowspan || 1;
                const colspan = cellNode.attrs.colspan || 1;
                const attrs: Record<string, string> = {};

                if (col === 0) {
                  attrs["data-first-column"] = "true";
                }

                // Mark cells that extend into the last column (accounting for colspan)
                if (col + colspan >= map.width) {
                  attrs["data-last-column"] = "true";
                }

                // Mark cells that extend into the last row (accounting for rowspan)
                if (row + rowspan >= map.height) {
                  attrs["data-last-row"] = "true";
                }

                if (Object.keys(attrs).length > 0) {
                  decorations.push(
                    Decoration.node(
                      pos + cellPos + 1,
                      pos + cellPos + 1 + cellNode.nodeSize,
                      attrs
                    )
                  );
                }
              }
            }
          });
        }
      });

      return DecorationSet.create(doc, decorations);
    };

    return [
      new Plugin({
        key: new PluginKey("table-cell-attributes"),
        state: {
          init: (_, state) => createCellDecorations(state),
          apply: (tr, pluginState, oldState, newState) => {
            // Only recompute if document changed
            if (!tr.docChanged) {
              return pluginState;
            }

            return createCellDecorations(newState);
          },
        },
        props: {
          decorations(state) {
            return this.getState(state);
          },
        },
      }),
      new Plugin({
        key: new PluginKey("table-cell-copy-transform"),
        props: {
          transformCopied: (slice) => {
            // check if the copied selection is a single table, with a single row, with a single cell. If so,
            // copy the cell content only – not a table with a single cell. This leads to more predictable pasting
            // behavior, both in and outside the app.
            if (slice.content.childCount === 1) {
              const table = slice.content.firstChild;
              if (
                table?.type.spec.tableRole === "table" &&
                table.childCount === 1
              ) {
                const row = table.firstChild;
                if (
                  row?.type.spec.tableRole === "row" &&
                  row.childCount === 1
                ) {
                  const cell = row.firstChild;
                  if (cell?.type.spec.tableRole === "cell") {
                    return new Slice(
                      cell.content,
                      slice.openStart,
                      slice.openEnd
                    );
                  }
                }
              }
            }

            return slice;
          },
        },
      }),
    ];
  }
}
