import React, { useEffect, useState } from "react";
import "./App.css";
import GrammarList from "./components/GrammarList";
import GrammarAdder from "./components/GrammarAdder";
import { Searches } from "./components/Searches";
import { MdRefresh } from "react-icons/md";
import { Grammar,Rule,FileHolder,RightSide } from "./types";
export function App() {
  const [grammars, setGramars] = useState<Grammar[]>([]);
  const [add, setAdd] = useState(false);
  const [filteredGrammars, setFilteredGrammars] = useState<Grammar[] | null>(
    null
  );

  const getGrammars = async () => {
    try {
      const resp = await fetch("/api/grammars");
      const newGrammar = await resp.json();
      setGramars(newGrammar);
    } catch (error) {
      alert(error);
    }
  };
  useEffect(() => {
    getGrammars();
  }, []);

  const deleteGramar = async (id: number) => {
    await fetch("/api/grammars/" + id, {
      method: "DELETE",
    });
    setGramars(grammars.filter((grammar) => grammar.id !== id));
  };

  const showForm = () => {
    setAdd(!add);
  };

  const duplicateGr = async (id: number) => {
    const response = await fetch("/api/grammars/" + id, {
      method: "POST",
    });
    const grammar = await response.json();
    setGramars([...grammars, grammar]);
  };

  const save = async (uri: string, rules: Rule[], rootRule: Rule | null) => {
    const response = await fetch("/api/grammars/", {
      method: "POST",
      headers: {
        "Content-type": "application/json",
      },
      body: JSON.stringify({
        id: null,
        uri: uri,
        rules: rules,
        rootRule: rootRule,
      }),
    });
    if (response.status !== 201) {
      setAdd(false);
      return;
    }
    const grammar = await response.json();
    if (response.status !== 201) {
      alert(grammar.message);
      return;
    }
    setGramars([...grammars, grammar]);

    setAdd(false);
  };

  const editGramar = async (Grammar: { uri: string; id: number }) => {
    const response = await fetch("/api/grammars/" + Grammar.id + "/uri/", {
      method: "PUT",
      headers: {
        "Content-type": "application/json",
      },
      body: JSON.stringify(Grammar),
    });
    const resp_grammar = await response.json();

    setGramars(
      grammars.map((grammar) =>
        grammar.id === Grammar.id ? resp_grammar : grammar
      )
    );
  };

  const getGrammarRules = async (grammarId: number) => {
    const response = await fetch("/api/rules/grammar/" + grammarId);

    const rulesNew:Rule[] = await response.json();
    setGramars(
      grammars.map((grammar) =>
        grammar.id === grammarId ? { ...grammar, rules: rulesNew, rootRule:rulesNew.filter((rule=>rule.root)).at(0) } : grammar
      )
    );
  };

  const addRule = async (rule: Rule, grammarId: number) => {
    const response = await fetch("/api/rules/" + grammarId, {
      method: "POST",
      headers: {
        "Content-type": "application/json",
      },
      body: JSON.stringify(rule),
    });
    const newRule = await response.json();
    if (!(response.status === 201 || response.status === 200)) {
      alert(newRule.message);
      return;
    }
    if (newRule.root) {
      setGramars(
        grammars.map((grammar) =>
          grammar.id === grammarId ? (grammar.rootRule = newRule) : grammar
        )
      );
    }
    if (response.status === 201) {
      setGramars(
        grammars.map((grammar) =>
          grammar.id === grammarId
            ? { ...grammar, rules: [...grammar.rules, newRule] }
            : grammar
        )
      );
      return;
    }
    if (response.status === 200) {
      setGramars(
        grammars.map((grammar) =>
          grammar.id === grammarId
            ? {
                ...grammar,
                rules: grammar.rules.map((rule) =>
                  newRule.id === rule.id ? newRule : rule
                ),
              }
            : grammar
        )
      );
      return;
    }
  };
  const deleteRule = async (
    ruleId: number,
    root: boolean,
    grammarId: number
  ) => {
    await fetch("/api/rules/" + ruleId, {
      method: "DELETE",
    });

    setGramars(
      grammars.map((grammar) =>
        grammar.id === grammarId
          ? {
              ...grammar,
              rules: grammar.rules.filter((rule) => rule.id !== ruleId),
              rootRule: root ? undefined : grammar.rootRule,
            }
          : grammar
      )
    );
  };

  const deleteFilesFromGrammar = async (id: number) => {
    let res = window.confirm(
      "Confirm clearing of files from grammar id: " + id
    );
    if (res) {
      console.log("clear-confirmed");
    } else {
      return;
    }
    await fetch("/api/files/grammar/" + id, {
      method: "DELETE",
    });
    setGramars(
      grammars.map((grammar) =>
        grammar.id === id ? { ...grammar, files: [] } : grammar
      )
    );
  };

  const clear = async () => {
    let res = window.confirm("Confirm clearing of grammars");
    if (res) {
      console.log("clear-confirmed");
    } else {
      return;
    }
    await fetch("/api/grammars/all", {
      method: "DELETE",
    });
    setGramars([]);
  };

  const changeRule = async (
    ruleId: number,
    rule: { leftSymbol: string | null; rightSide: RightSide[] | null },
    grammarId: number
  ) => {
     await fetch("/api/rules/" + ruleId, {
      method: "PUT",
      headers: {
        "Content-type": "application/json",
      },
      body: JSON.stringify(rule),
    });
    
    getGrammarRules(grammarId);
  };

  const addFromFile = async (
    file: string,
    xml: boolean | null,
    save: boolean
  ) => {
    let header = {
      "Content-type": xml ? "aplication/xml" : "text/plain",
    };
    let endpoint;
    if (xml == null) {
      endpoint = "/api/files/?save=" + save;
    } else {
      endpoint =
        "/api/files/?format=" + (xml ? "xml" : "jsgf") + "&save=" + save;
    }

    const response = await fetch(endpoint, {
      method: "POST",
      headers: header,
      body: file,
    });

    const grammar = await response.json();
    if (response.status !== 201) {
      alert(grammar.message);
      return;
    }
    setGramars([...grammars, grammar]);
  };

  const getFile = async (id: number, grammarId: number) => {
    let endpoint = "/api/files/" + id;
    const response = await fetch(endpoint);
    const text = await response.text();
    setGramars(
      grammars.map((grammar) =>
        grammarId === grammar.id
          ? {
              ...grammar,
              files: grammar.files.map((file) =>
                file.id === id ? { ...file, content: text } : file
              ),
            }
          : grammar
      )
    );
  };
  const getGrammarFiles = async (id: number) => {
    const response = await fetch("/api/files/grammar/" + id);
    return await response.json();
  };

  const generateFile = async (
    xml: boolean,
    grammarId: number,
    save: boolean
  ) => {
    let endpoint =
      "/api/files/grammar/" +
      grammarId +
      "/generated?format=" +
      (xml ? "xml" : "jsgf");
    const response = await fetch(endpoint, { method: save ? "PUT" : "GET" });
    const text = await response.text();
    let grammarData: {
      files?: FileHolder[];
      xmlGen?: string;
      jsgfGen?: string;
    } = xml
      ? {
          xmlGen: text,
        }
      : { jsgfGen: text };
    if (save) {
      const savedContents = saveContents(grammarId);
      const grammarFiles = await getGrammarFiles(grammarId);
      grammarData = {
        ...grammarData,
        files: contentToFileHolders(grammarFiles, savedContents),
      };
    }
    setGramars(
      grammars.map((grammar) =>
        grammarId === grammar.id ? { ...grammar, ...grammarData } : grammar
      )
    );
  };
  const saveContents = (grammarId: number) => {
    const result: { [key: number]: string | undefined } = {};
    grammars.forEach((grammar) => {
      if (grammar.id === grammarId) {
        grammar.files.map((file) => (result[file.id] = file.content));
      }
    });
    return result;
  };
  const contentToFileHolders = (
    files: FileHolder[],
    savedContents: { [key: number]: string | undefined }
  ) => {
    return  files.map((file) => ({
      ...file,
      content: savedContents[file.id],
    }));
  };

  const fileToGrammar = async (
    file: string,
    xml: boolean | null,
    grammarId: number,
    save: boolean,
    diff: boolean
  ) => {
    let header = {
      "Content-type": xml ? "aplication/xml" : "text/plain",
    };
    let endpoint =
      "/api/files/" +
      grammarId +
      "/?format=" +
      (xml ? "xml" : "jsgf") +
      "&save=" +
      save +
      "&newGrammar=" +
      diff;
    const response = await fetch(endpoint, {
      method: "POST",
      headers: header,
      body: file,
    });

    if (response.status === 204) {
      alert("different grammar");
      return;
    }
    const grammarNew: Grammar & { message: string } = await response.json();
    if (response.status === 201) {
      setGramars([...grammars, grammarNew]);
      return;
    }

    if (response.status === 200) {
      const savedContents = saveContents(grammarId);
      setGramars(
        grammars.map((grammar) =>
          grammar.id === grammarId
            ? {
                ...grammar,
                files: contentToFileHolders(grammarNew.files, savedContents),
              }
            : grammar
        )
      );
      return;
    }
    alert(grammarNew.message);
  };
  const deleteFile = async (fileId: number, grammarId: number) => {
    await fetch("/api/files/" + fileId, {
      method: "DELETE",
    });
    setGramars(
      grammars.map((grammar) =>
        grammar.id === grammarId
          ? {
              ...grammar,
              files: grammar.files.filter((file) => file.id !== fileId),
            }
          : grammar
      )
    );
  };

  const changeRoot = async (id: number, grammarId: number, oldRule: Rule) => {
    let newRule = { root: !oldRule.root };
    await fetch("/api/rules/" + id, {
      method: "PUT",
      headers: {
        "Content-type": "application/json",
      },
      body: JSON.stringify(newRule),
    });
    let root = oldRule.root;
    setGramars(
      grammars.map((grammar) =>
        grammar.id === grammarId
          ? {
              ...grammar,
              rootRule: root ? undefined : oldRule,
              rules: grammar.rules.map((rule) =>
                rule.id === id ? { ...rule, root: !root } : rule
              ),
            }
          : grammar
      )
    );
  };

  return (
    <div className="App">
      <h1>
        Grammar Manager <MdRefresh onClick={getGrammars} />
      </h1>

      <Searches setFilteredGrammars={setFilteredGrammars} grammars={grammars} />

      <button onClick={clear}>CLEAR</button>
      {grammars.length !== 0 &&
      (filteredGrammars === null || filteredGrammars.length !== 0) ? (
        <GrammarList
          grammars={filteredGrammars === null ? grammars : filteredGrammars}
          deleteGr={deleteGramar}
          editGr={editGramar}
          addRule={addRule}
          duplicateGr={duplicateGr}
          deleteRule={deleteRule}
          changeRule={changeRule}
          generateFile={generateFile}
          changeRoot={changeRoot}
          getFile={getFile}
          fileToGrammar={fileToGrammar}
          deleteFile={deleteFile}
          deleteFilesFromGrammar={deleteFilesFromGrammar}
        />
      ) : (
        "No grammars to show"
      )}
      <button onClick={showForm}>{add ? "CANCEL" : "ADD GRAMMAR"}</button>
      {add && <GrammarAdder save={save} fileAdd={addFromFile} />}
      <footer>
        <h1>PADDING</h1>
      </footer>
    </div>
  );
}
