import { EditorState, TextSelection } from "prosemirror-state";
import { schema } from "prosemirror-schema-basic";
import { EditorView } from "prosemirror-view";
import { Schema, DOMParser, DOMSerializer } from "prosemirror-model";
import { Emitter, camelCase, minMax } from "./Utils";
import { getNodeAttrs, markIsActive, nodeIsActive } from "../utils";

class ABC implements Emitter {
    _callbacks: unknown;

    on(event, fn) {
      this._callbacks = this._callbacks || {};
      // Create namespace for this event
      if (!this._callbacks[event]) {
        this._callbacks[event] = [];
      }
      this._callbacks[event].push(fn);
      return this;
    }

    emit(event, ...args) {
      this._callbacks = this._callbacks || {};
      const callbacks = this._callbacks[event];

      if (callbacks) {
        callbacks.forEach((callback) => callback.apply(this, args));
      }

      return this;
    }


    // Remove event listener for given event.
    // If fn is not provided, all event listeners for that event will be removed.
    // If neither is provided, all event listeners will be removed.
    off(event, fn) {
      if (!arguments.length) {
        this._callbacks = {};
      } else {
        // event listeners for the given event
        const callbacks = this._callbacks ? this._callbacks[event] : null;
        if (callbacks) {
          if (fn) {
            this._callbacks[event] = callbacks.filter((cb) => cb !== fn); // remove specific handler
          } else {
            delete this._callbacks[event]; // remove all handlers
          }
        }
      }

      return this;
    }

    name: string

    state: EditorState<unknown>;

    // extensions: unknown =null

    events = [
      "init",
      "transaction",
      "update",
      "focus",
      "blur",
      "paste",
      "drop",
    ]

    schema: unknown;

    view: EditorView

    public get view_1() {
      return this.view;
    }

    selection = { from: 0, to: 0 }

    constructor(el: HTMLElement, _state: EditorState<unknown>, schema1: unknown, refView: MutableRef<unknown>, updateV: Function) {
      // super();
      // this.extensions = extensions;
      // this.schema = schema;
      // const options = {
      //   onUpdate: updateV,
      // };
      // this.events.forEach((name) => {
      //   this.on(name, options[camelCase(`on ${name}`)] || (() => {}));
      // });
      // console.log("this.", this._callbacks);
      //
      // console.log("el");
      const statebase = EditorState.create({ schema });
      this.state = statebase;
      this.dispatchTransaction = this.dispatchTransaction.bind(this);
      const view = new EditorView(el, {
        state: statebase,
        dispatchTransaction: this.dispatchTransaction1.bind(this),
      });
      this.view = view;
      // console.log("this.", this.view);
      // refView.current = this.view;
      // this.setActiveNodesAndMarks = this.setActiveNodesAndMarks.bind(this);
      // this.setActiveNodesAndMarks();
    }

    options = {
      emptyDocument: {
        type: "doc",
        content: [{
          type: "paragraph",
        }],
      },
      parseOptions: {},
    }

    dispatchTransaction1(transaction) {
      console.log("Document size went from", transaction.before.content.size,
        "to", transaction.doc.content.size);
      const newState = this.view.state.apply(transaction);
      this.view.updateState(newState);
      console.log("***");
    }

    createDocument(content, parseOptions = this.options.parseOptions) {
      if (content === null) {
        return this.schema.nodeFromJSON(this.options.emptyDocument);
      }

      if (typeof content === "object") {
        try {
          return this.schema.nodeFromJSON(content);
        } catch (error) {
          console.warn("[tiptap warn]: Invalid content.", "Passed value:", content, "Error:", error);
          return this.schema.nodeFromJSON(this.options.emptyDocument);
        }
      }

      if (typeof content === "string") {
        const element = document.createElement("div");
        element.innerHTML = content.trim();

        return DOMParser.fromSchema(this.schema).parse(element, parseOptions);
      }

      return false;
    }


    dispatchTransaction(transaction) {
      console.log("dispatchTransaction", transaction);
      console.log("Document size went from", transaction.before.content.size,
        "to", transaction.doc.content.size);
      const newState = this.state.apply(transaction);
      this.view.updateState(newState);
      //
      // this.selection = {
      //   from: this.state.selection.from,
      //   to: this.state.selection.to,
      // };
      // this.setActiveNodesAndMarks();
      //
      // this.emit("transaction", {
      //   getHTML: this.getHTML.bind(this),
      //   getJSON: this.getJSON.bind(this),
      //   state: this.state,
      //   transaction,
      // });
      //
      // if (!transaction.docChanged || transaction.getMeta("preventUpdate")) {
      //   return;
      // }
      //
      // this.emitUpdate(transaction);
    }

    emitUpdate(transaction) {
      this.emit("update", {
        getHTML: this.getHTML.bind(this),
        getJSON: this.getJSON.bind(this),
        state: this.state,
        transaction,
      });
    }

    resolveSelection(position = null) {
      if (this.selection && position === null) {
        return this.selection;
      }

      if (position === "start" || position === true) {
        return {
          from: 0,
          to: 0,
        };
      }

      if (position === "end") {
        const { doc } = this.state;
        return {
          from: doc.content.size,
          to: doc.content.size,
        };
      }

      return {
        from: position,
        to: position,
      };
    }

    focus(position = null) {
      console.log("focus");
      if ((this.view.focused && position === null) || position === false) {
        return;
      }

      const { from, to } = this.resolveSelection(position);

      this.setSelection(from, to);
      setTimeout(() => this.view.focus(), 10);
    }

    setSelection(from = 0, to = 0) {
      console.log("setS");
      const { doc, tr } = this.state;
      const resolvedFrom = minMax(from, 0, doc.content.size);
      const resolvedEnd = minMax(to, 0, doc.content.size);
      const selection = TextSelection.create(doc, resolvedFrom, resolvedEnd);
      const transaction = tr.setSelection(selection);

      this.view.dispatch(transaction);
    }

    blur() {
      this.view.dom.blur();
    }

    getSchemaJSON() {
      return JSON.parse(JSON.stringify({
        nodes: this.extensions.nodes,
        marks: this.extensions.marks,
      }));
    }

    getHTML() {
      const div = document.createElement("div");
      const fragment = DOMSerializer
        .fromSchema(this.schema)
        .serializeFragment(this.state.doc.content);

      div.appendChild(fragment);

      return div.innerHTML;
    }

    getJSON() {
      return this.state.doc.toJSON();
    }

    // setContent(content = {}, emitUpdate = false, parseOptions) {
    //   const { doc, tr } = this.state;
    //   const document = this.createDocument(content, parseOptions);
    //   const selection = TextSelection.create(doc, 0, doc.content.size);
    //   const transaction = tr
    //     .setSelection(selection)
    //     .replaceSelectionWith(document, false)
    //     .setMeta("preventUpdate", !emitUpdate);
    //
    //   this.view.dispatch(transaction);
    // }

    clearContent(emitUpdate = false) {
      // this.setContent(this.options.emptyDocument, emitUpdate);
    }

    activeMarks = {}

    activeMarkAttrs = {}

    activeNodes ={}

    getMarkAttrs(type = null) {
      return this.activeMarkAttrs[type];
    }


    setActiveNodesAndMarks() {
      this.activeMarks = Object
        .entries(this.schema.marks)
        .reduce((marks, [name, mark]) => ({
          ...marks,
          [name]: (attrs = {}) => markIsActive(this.state, mark, attrs),
        }), {});

      this.activeMarkAttrs = Object
        .entries(this.schema.marks)
        .reduce((marks, [name, mark]) => ({
          ...marks,
          [name]: this.getMarkAttrs(this.state, mark),
        }), {});

      this.activeNodes = Object
        .entries(this.schema.nodes)
        .reduce((nodes, [name, node]) => ({
          ...nodes,
          [name]: (attrs = {}) => nodeIsActive(this.state, node, attrs),
        }), {});
    }

    getNodeAttrs1(type = null) {
      return {
        ...getNodeAttrs(this.state, this.schema.nodes[type]),
      };
    }

    get isActive() {
      return Object
        .entries({
          ...this.activeMarks,
          ...this.activeNodes,
        })
        .reduce((types, [name, value]) => ({
          ...types,
          [name]: (attrs = {}) => value(attrs),
        }), {});
    }

    focused: boolean

    public focus1(view, event) {
      this.focused = true;
      this.emit("focus", {
        event,
        state: this.view.state,
        view,
      });

      const transaction = this.state.tr.setMeta("focused", true);
      this.view.dispatch(transaction);
    }

    public blur1(view, event) {
      this.focused = false;
      this.emit("blur", {
        event,
        state: view.state,
        view,
      });

      const transaction = this.state.tr.setMeta("focused", false);
      this.view.dispatch(transaction);
    }
}
export { ABC };
