import React from "react";
import { get, post } from "@/common";
import { collection } from "@/store";

interface Conflicts {
  [index: string]: HTMLInputElement[];
}
interface ConflictsTmp {
  [index: string]: Set<string>;
}
interface IndexToElement {
  [index: string]: HTMLInputElement;
}
interface IndexToCell {
  [index: string]: HTMLTableCellElement;
}

const indexToElement: IndexToElement = {};
const indexToCell: IndexToCell = {};
const conflicts: Conflicts = {};
const marks: (HTMLInputElement | null)[][] = [];
const tds: (HTMLTableCellElement | null)[][] = [];

const downloadObjectAsJson = (exportObj: any, exportName: string) => {
  const dataStr =
    "data:text/json;charset=utf-8," +
    encodeURIComponent(JSON.stringify(exportObj));
  const downloadAnchorNode = document.createElement("a");
  downloadAnchorNode.setAttribute("href", dataStr);
  downloadAnchorNode.setAttribute("download", `${exportName}.json`);
  document.body.appendChild(downloadAnchorNode); // required for firefox
  downloadAnchorNode.click();
  downloadAnchorNode.remove();
};

const Timetable = ({
  user,
  disableNetwork = false,
  disableConflictCheck = false,
  replaceInputType = "checkbox",
  apiRecordEndPoint = "/api/record",
  openRecordMode = false,
  hideDownloadButton = true,
}) => {
  const [editable, setEditable] = React.useState(true);
  const ref = React.useRef();

  const handleSelect = async (event: Event) => {
    const target: HTMLInputElement = event.target;
    console.log("select", target.name, target.checked);

    if (disableConflictCheck) return;

    const changedInputs: any = [];
    // find whether there are checked input in conflict
    for (const input of conflicts[target.name]) {
      if (input.name === target.name) continue;
      if (input.checked) {
        alert("Error: Conflict select");
        location.reload();
        return;
      }
    }
    for (const input of conflicts[target.name]) {
      if (input.name === target.name) continue;
      if (target.checked) {
        if (input.getAttribute("disabled") === null) {
          input.setAttribute("disabled", "true");
          changedInputs.push({ input, disable: false });
        }
      } else {
        if (input.getAttribute("disabled") === "true") {
          input.removeAttribute("disabled");
          changedInputs.push({ input, disable: true });
        }
      }
    }

    if (disableNetwork) return;

    // post request
    const json = await post("/api/record", {
      name: target.name,
      checked: target.checked,
      user,
    });
    if (json.error !== undefined) {
      alert(json.error);
      // revert conflict changed input
      for (const { input, disable } of changedInputs) {
        if (disable) {
          input.setAttribute("disabled", "true");
        } else {
          input.removeAttribute("disabled");
        }
      }
    }
  };

  const handleInput = (event: React.ChangeEvent<HTMLInputElement>): boolean => {
    const { target } = event;
    // validate
    if (target?.children[0]?.tagName !== "TABLE") {
      console.log("not a table");
      return false;
    }

    // turn off editable
    setEditable(false);

    // empty marks
    marks.length = 0;
    // empty tds
    tds.length = 0;

    const table = target.children[0];
    table.setAttribute("border", "1");

    // mark cell
    const conflictsTmp: ConflictsTmp = {};
    const tbody = table.children[table.children.length - 1];
    for (const tr_index in tbody.children) {
      const tr = tbody.children[tr_index];
      const row: (HTMLInputElement | null)[] = [];
      const rowTD: (HTMLTableCellElement | null)[] = [];
      for (const td_index in tr.children) {
        const td: HTMLTableCellElement = tr.children[td_index];
        if (td.tagName !== "TD") continue;
        if (td.getAttribute("bgcolor")?.toUpperCase() !== "#39CEFF") {
          row.push(null);
          continue;
        }

        const index = `${tr_index},${td_index}`;

        const placeholders = td.textContent?.trim().split(",");
        if (placeholders === undefined) continue;
        if (conflictsTmp[index] === undefined) conflictsTmp[index] = new Set();
        for (const ph of placeholders) conflictsTmp[index].add(ph);

        // mount click event
        const input = document.createElement("input");
        input.setAttribute("type", replaceInputType);
        input.onchange = handleSelect;
        input.name = index;
        td.innerHTML = "";
        td.appendChild(input);
        indexToElement[index] = input;
        indexToCell[index] = td;

        row.push(input);
        rowTD.push(td);
      }
      marks.push(row);
      tds.push(rowTD);
      // console.log("marks", marks);
    }

    // resolve conflicts
    for (const index in conflictsTmp) {
      if (conflicts[index] === undefined) conflicts[index] = [];
      for (const ph of Array.from(conflictsTmp[index])) {
        for (const conflictIndex in conflictsTmp) {
          if (conflictsTmp[conflictIndex].has(ph))
            conflicts[index].push(indexToElement[conflictIndex]);
        }
      }
    }

    console.log(conflicts);

    return true;
  };

  const refresh = async () => {
    const json = await get(`${apiRecordEndPoint}?name=${user}`);
    if (openRecordMode) {
      for (const index in json) {
        const input = indexToElement[index];
        const td = indexToCell[index];
        if (json[index] !== user) {
          td.innerHTML = json[index];
          td.removeAttribute("bgcolor");
        } else {
          input.checked = true;
          input.disabled = true;
        }
      }
      return;
    }
    const occupied: string[] = json.occupied;
    const myselect: string[] = json.myselect;
    console.log(json);
    for (const index in indexToElement) {
      if (occupied.includes(index)) {
        indexToElement[index].style.display = "none";
      } else {
        indexToElement[index].style.display = "";
      }
      const includes = myselect.includes(index);
      indexToElement[index].checked = includes;
      // after checked, find conflicts input
      if (disableConflictCheck) continue;
      if (includes) {
        for (const input of conflicts[index]) {
          if (input.name === index) continue;
          input.setAttribute("disabled", "true");
        }
      }
    }
  };
  React.useEffect(() => {
    if (disableNetwork) return;

    const interval = setInterval(() => {
      refresh();
    }, 1000);
    return () => {
      clearInterval(interval);
    };
  }, []);

  React.useEffect(() => {
    const main = async () => {
      const json = await get("/api/html");
      ref.current.innerHTML = json.html;
      handleInput({ target: ref.current });
      refresh();
    };
    main();
  }, []);

  const DownloadMarks = async () => {
    console.log("download marks", marks);
    const data = {
      user,
      selections: {},
    };
    for (const row of marks) {
      for (const input of row) {
        if (input === null) continue;
        if (input.checked) {
          data.selections[input.name] = 1;
        } else if (parseFloat(input.value)) {
          data.selections[input.name] = parseFloat(input.value);
        }
      }
    }
    console.log(data);
    downloadObjectAsJson(data, user);
  };

  return (
    <>
      <h2 style={{ textAlign: "center" }}>Login as {user}</h2>
      <div
        ref={ref}
        contentEditable={editable}
        style={{
          overflow: "scroll",
          display: "flex",
          justifyContent: "center",
        }}
        onInput={handleInput}
      ></div>{" "}
      {!hideDownloadButton && (
        <p style={{ display: "flex", justifyContent: "center" }}>
          <button onClick={DownloadMarks}>Download Selection</button>
        </p>
      )}
      <div style={{ display: "none" }} id="download-dom"></div>
    </>
  );
};

export default Timetable;
