import DOMPurify from "dompurify";

export interface FileConfig {
  dragDropClassname: string;
  inputSelector: string;
  resultsSelector: string;
  deleteButtonSelector: string;
  deleteFileAttr: string;
  fileTemplate: (name: string, i: number) => string;
}

export const defaultConfig: FileConfig = {
  dragDropClassname: "is-dragdrop",
  inputSelector: 'input[type="file"]',
  resultsSelector: ".file-input__files",
  deleteButtonSelector: "[data-file-delete]",
  deleteFileAttr: "data-file-delete",
  fileTemplate: (name: string, i: number) => {
    return `
      <li>${name}&nbsp;<button type="button" data-file-delete="${i}" class="file-input__delete">
          <svg class="icon icon--small icon--trash icon--danger" aria-hidden="true">
            <use xlink:href="/sprite.svg#trash"></use>
          </svg>
          <span class="sr-only">Odstrániť súbor ${name}</span>
        </button>
      </li>
    `;
  },
};

export default class FileStatic {
  element: HTMLElement;
  config: FileConfig;
  input: HTMLInputElement | null;
  files: File[];
  results: HTMLElement | null;
  removeButtons: HTMLButtonElement[];

  constructor(element: HTMLElement, config?: Partial<FileConfig>) {
    this.element = element;
    this.config = { ...defaultConfig, ...config };
    this.input = null;
    this.files = [];
    this.results = null;
    this.removeButtons = [];

    this.onInputChange = this.onInputChange.bind(this);
    this.onRemoveButtonClick = this.onRemoveButtonClick.bind(this);
    this.highlight = this.highlight.bind(this);
    this.unhighlight = this.unhighlight.bind(this);
    this.onDrop = this.onDrop.bind(this);
    this.preventDefaults = this.preventDefaults.bind(this);

    this.init();
    return this;
  }

  init() {
    this.input = this.element.querySelector(
      this.config.inputSelector,
    ) as HTMLInputElement | null;
    this.results = this.element.querySelector(
      this.config.resultsSelector,
    ) as HTMLElement | null;
    if (!this.input || !this.results) return;

    this.input.addEventListener("change", this.onInputChange);

    ["dragenter", "dragover", "dragleave", "drop"].forEach((eventName) => {
      this.element.addEventListener(eventName, this.preventDefaults, false);
      document.body.addEventListener(eventName, this.preventDefaults, false);
    });

    ["dragenter", "dragover"].forEach((eventName) => {
      this.element.addEventListener(eventName, this.highlight, false);
    });

    ["dragleave", "drop"].forEach((eventName) => {
      this.element.addEventListener(eventName, this.unhighlight, false);
    });

    this.element.addEventListener("drop", this.onDrop);
  }

  onInputChange() {
    if (!this.input || !this.results) return false;
    this.files = Array.from(this.input.files ?? []);
    if (!this.files) return false;
    this.results.innerHTML = "";
    const listItems = this.files.map((file, i) =>
      this.config.fileTemplate(
        DOMPurify.sanitize(file.name, { ALLOWED_TAGS: [] }),
        i,
      ),
    );
    listItems.forEach((listItem) => {
      this.results!.appendChild(
        DOMPurify.sanitize(listItem, {
          ADD_TAGS: ["use"],
          RETURN_DOM_FRAGMENT: true,
        }) as DocumentFragment,
      );
    });
    this.removeButtons = Array.from(
      this.results.querySelectorAll(this.config.deleteButtonSelector),
    ) as HTMLButtonElement[];
    this.removeButtons.forEach((button) =>
      button.addEventListener("click", this.onRemoveButtonClick),
    );
  }

  onDrop(e: DragEvent) {
    if (!this.input) return;
    this.input.files = e?.dataTransfer?.files || new DataTransfer().files;
    this.onInputChange();
  }

  onRemoveButtonClick(e: MouseEvent) {
    if (!this.input) return;
    const dt = new DataTransfer();
    const target = e.currentTarget as HTMLElement;
    const deletedFileIndex = Number(
      target.getAttribute(this.config.deleteFileAttr),
    );
    this.files.splice(deletedFileIndex, 1);
    this.files.forEach((file) => dt.items.add(file));
    this.input.files = dt.files;
    this.onInputChange();
  }

  highlight() {
    this.element.classList.add(this.config.dragDropClassname);
  }

  unhighlight() {
    this.element.classList.remove(this.config.dragDropClassname);
  }

  preventDefaults(e: Event) {
    e.preventDefault();
    e.stopPropagation();
  }

  destroy() {
    if (!this.input) return;
    this.input.removeEventListener("change", this.onInputChange);
    ["dragenter", "dragover", "dragleave", "drop"].forEach((eventName) => {
      this.element.removeEventListener(eventName, this.preventDefaults, false);
      document.body.removeEventListener(eventName, this.preventDefaults, false);
    });
    ["dragenter", "dragover"].forEach((eventName) => {
      this.element.removeEventListener(eventName, this.highlight, false);
    });
    ["dragleave", "drop"].forEach((eventName) => {
      this.element.removeEventListener(eventName, this.unhighlight, false);
    });
    this.element.removeEventListener("drop", this.onDrop);
  }

  update() {
    this.destroy();
    this.init();
  }
}
