import { createStore } from "vuex";
import {
  getHouseholdByPerson,
  getCoursesByPerson,
  getRelationshipsByPerson,
} from "./api";
import {
  getHouseholdWidth,
  getRelationshipLabel,
  getRelationshipTitle,
  getRelationshipDirection,
  splitId,
  getAge,
} from "./vis-network";
import { visMessages } from "./i18n";
import {
  darkBlue,
  darkBrown,
  darkGreen,
  lightBlue,
  lightBrown,
  lightGreen,
} from "./colors";

const debug = process.env.NODE_ENV !== "production";

const store = createStore({
  strict: debug,
  state: {
    persons: [],
    households: [],
    courses: [],
    relationships: [],
    links: [],
    whitelistIds: [],
    personLoadedIds: [],
    householdLoadingIds: [],
    courseLoadedIds: [],
    relationshipLoadedIds: [],
    excludedNodesIds: [],
    updateHack: 0,
  },
  getters: {
    nodes(state) {
      let nodes = [];
      state.persons.forEach((p) => {
        nodes.push(p);
      });
      state.households.forEach((h) => {
        nodes.push(h);
      });
      state.courses.forEach((c) => {
        nodes.push(c);
      });
      // except excluded nodes (unchecked layers)
      state.excludedNodesIds.forEach((excluded) => {
        nodes = nodes.filter((n) => n.id !== excluded);
      });
      return nodes;
    },
    edges(state) {
      return state.links;
    },
    isInWhitelist: (state) => (person_id) => {
      return state.whitelistIds.includes(person_id);
    },
    isHouseholdLoading: (state) => (household_id) => {
      return state.householdLoadingIds.includes(household_id);
    },
    isCourseLoaded: (state) => (course_id) => {
      return state.courseLoadedIds.includes(course_id);
    },
    isRelationshipLoaded: (state) => (relationship_id) => {
      return state.relationshipLoadedIds.includes(relationship_id);
    },
    isPersonLoaded: (state) => (person_id) => {
      return state.personLoadedIds.includes(person_id);
    },
    isExcludedNode: (state) => (id) => {
      return state.excludedNodesIds.includes(id);
    },

    countLinksByNode: (state) => (node_id) => {
      let array = [];
      state.links
        .filter((link) => !link.id.startsWith("relationship"))
        .forEach((link) => {
          if (link.from === node_id || link.to === node_id) {
            if (
              state.excludedNodesIds.indexOf(splitId(link.id, "link")) === -1
            ) {
              array.push(link);
            }
            //console.log(link.id, state.excludedNodesIds.indexOf(splitId(link.id, 'link')))
          }
        });
      //console.log('count links', array.length, array.map(i => i.id))
      return array.length;
    },

    getParticipationsByCourse: (state) => (course_id) => {
      const course = state.courses.filter((c) => c.id === course_id)[0];
      const currentParticipations = course.participations.filter(
        (p) => p.endDate === null,
      );
      //console.log('get persons in', course_id, currentParticipations.map(p => p.person.id),
      //    'with folded', currentParticipations.filter(p => p.person.folded === true).map(p => p.person.id))
      return currentParticipations;
    },

    getMembersByHousehold: (state) => (household_id) => {
      const household = state.households.filter(
        (h) => h.id === household_id,
      )[0];
      const currentMembers = household.members.filter((m) =>
        household.current_members_id.includes(m.id),
      );
      //console.log('get persons in', household_id, currentMembers.map(m => m.person.id),
      //    'with folded', currentMembers.filter(m => m.person.folded === true).map(m => m.person.id))
      return currentMembers;
    },

    /**
     * This getter is a little bit mysterious :
     * The 2 previous getters return complete array, but folded (missing) persons are not taken into consideration and are not displayed (!?!)
     * This getter compare input array (participations|members) to personLoadedIds array
     * and return complete array with folded persons taken into consideration
     *
     * @param state
     * @param array - An array of persons from course or household.
     *         This array is dirty, melting persons adapted (or not) to vis, with _id and _label.
     * @return array - An array of persons mapped and taken in state.persons
     */
    getPersonsGroup: (state) => (array) => {
      let group = [];
      array.forEach((item) => {
        let id = splitId(item.person.id, "id");
        if (state.personLoadedIds.includes(id)) {
          group.push(state.persons.filter((person) => person._id === id)[0]);
        }
      });
      //console.log('array', array.map(item => item.person.id))
      //console.log('get persons group', group.map(f => f.id))
      return group;
    },

    getPersonById: (state) => (person_id) => {
      return state.persons.find((p) => p._id === person_id);
    },
  },
  mutations: {
    addPerson(state, [person, options]) {
      if ((!"_id") in person) {
        person._id = person.id;
        person.id = `person_${person.id}`;
      }

      let existing = state.persons.find((p) => p._id === person._id);

      if (typeof existing !== "undefined") {
        if (!options.folded && person.folded) {
          // unfold
        }
        return;
      }
      let age = getAge(person);
      let separator = person.gender === null ? "" : " - ";
      age = age === "" ? "" : separator + age;

      let debug = "";
      /// Debug mode: uncomment to display person_id on visgraph
      //debug = `\nid ${person.id}`

      person.group = person.type;
      person._id = person.id;
      person.id = `person_${person.id}`;
      person.label = `*${person.text}${person.deathdate ? " (‡)" : ""}*\n_${person.gender === null ? "" : person.gender?.label}${age}_${debug}`;
      person.folded = false;
      // folded is used for missing persons
      if (options.folded) {
        person.title = visMessages.fr.visgraph.click_to_expand;
        person._label = person.label; // keep label
        person.label = null;
        person.folded = true;
      }
      state.persons.push(person);
    },
    addHousehold(state, household) {
      household.group = household.type;
      household._id = household.id;
      household.label = `${visMessages.fr.visgraph.Household} n° ${household.id}`;
      household.id = `household_${household.id}`;
      state.households.push(household);
    },
    addCourse(state, course) {
      course.group = course.type;
      course._id = course.id;
      course.label = `${visMessages.fr.visgraph.Course} n° ${course.id}`;
      course.id = `accompanying_period_${course.id}`;
      state.courses.push(course);
    },
    addRelationship(state, relationship) {
      relationship.group = relationship.type;
      relationship._id = relationship.id;
      relationship.id = `relationship_${relationship.id}`;
      state.relationships.push(relationship);
    },
    addLink(state, link) {
      state.links.push(link);
    },
    updateLink(state, link) {
      //console.log('updateLink', link)
      let link_ = {
        from: `person_${link.fromPerson.id}`,
        to: `person_${link.toPerson.id}`,
        id:
          "relationship_" +
          splitId(link.id, "id") +
          "-person_" +
          link.fromPerson.id +
          "-person_" +
          link.toPerson.id,
        arrows: getRelationshipDirection(link),
        color: lightGreen,
        font: { color: darkGreen },
        dashes: true,
        label: getRelationshipLabel(link),
        title: getRelationshipTitle(link),
        relation: link.relation,
        reverse: link.reverse,
      };
      // find row position and replace by updatedLink
      state.links.splice(
        state.links.findIndex((item) => item.id === link_.id),
        1,
        link_,
      );
    },
    removeLink(state, link_id) {
      state.links = state.links.filter((l) => l.id !== link_id);
    },

    //// id markers
    markInWhitelist(state, person) {
      state.whitelistIds.push(person.id);
    },
    markPersonLoaded(state, id) {
      state.personLoadedIds.push(id);
    },
    unmarkPersonLoaded(state, id) {
      state.personLoadedIds = state.personLoadedIds.filter((i) => i !== id);
    },
    markHouseholdLoading(state, id) {
      //console.log('..loading household', id)
      state.householdLoadingIds.push(id);
    },
    unmarkHouseholdLoading(state, id) {
      state.householdLoadingIds = state.householdLoadingIds.filter(
        (i) => i !== id,
      );
    },
    markCourseLoaded(state, id) {
      state.courseLoadedIds.push(id);
    },
    unmarkCourseLoaded(state, id) {
      state.courseLoadedIds = state.courseLoadedIds.filter((i) => i !== id);
    },
    markRelationshipLoaded(state, id) {
      state.relationshipLoadedIds.push(id);
    },
    unmarkRelationshipLoaded(state, id) {
      state.relationshipLoadedIds = state.relationshipLoadedIds.filter(
        (i) => i !== id,
      );
    },

    //// excluded
    addExcludedNode(state, id) {
      //console.log('==> exclude list: +', id)
      state.excludedNodesIds.push(id);
    },
    removeExcludedNode(state, id) {
      //console.log('<== exclude list: -', id)
      state.excludedNodesIds = state.excludedNodesIds.filter((e) => e !== id);
    },

    //// unfold
    unfoldPerson(state, person) {
      if (!person.folded) {
        return;
      }
      person.label = person._label;
      delete person._label;
      delete person.title;
      person.folded = false;
    },

    //// force update hack
    updateHack(state) {
      state.updateHack = state.updateHack + 1;
    },
  },
  actions: {
    /**
     *  Expand loop (steps 1->10), always start from a person.
     *  Fetch household, courses, relationships, and others persons.
     *  These persons are "missing" and will be first display in fold mode.
     *
     *  1) Add a new person
     *  @param object
     *  @param person
     */
    addPerson({ commit, dispatch }, person) {
      commit("markPersonLoaded", person.id);
      commit("addPerson", [person, { folded: false }]);
      commit("updateHack");
      dispatch("fetchInfoForPerson", person);
    },

    /**
     * Add a person manually
     *
     * @param commit
     * @param dispatch
     * @param person
     */
    addMorePerson({ commit, dispatch, getters }, person) {
      let nodeId = `person_${person.id}`;
      if (getters.isPersonLoaded(person.id)) {
        if (getters.isExcludedNode(nodeId)) {
          commit("removeExcludedNode", nodeId);
          let p = getters.getPersonById(person.id);
          if (typeof p !== "undefined") {
            commit("unfoldPerson", p);
          } else {
            throw "a person loaded was not found";
          }
          commit("updateHack");
        }
      } else {
        return dispatch("addPerson", person);
      }
    },

    /**
     *  2) Fetch infos for this person (hub)
     *  @param object
     *  @param person
     */
    fetchInfoForPerson({ dispatch }, person) {
      // TODO enfants hors ménages
      // example: household 61
      //console.log(person.text, 'household', person.current_household_id)
      if (null !== person.current_household_id) {
        dispatch("fetchHouseholdForPerson", person);
      }
      dispatch("fetchCoursesByPerson", person);
      dispatch("fetchRelationshipByPerson", person);
    },

    /**
     *  3) Fetch person current household (if it is not already loading)
     *  check first isHouseholdLoading to fetch household once
     *  @param object
     *  @param person
     */
    fetchHouseholdForPerson({ commit, getters, dispatch }, person) {
      //console.log('  isHouseholdLoading ?', getters.isHouseholdLoading(person.current_household_id))
      if (!getters.isHouseholdLoading(person.current_household_id)) {
        commit("markHouseholdLoading", person.current_household_id);
        return getHouseholdByPerson(person)
          .then(
            (household) =>
              new Promise((resolve) => {
                commit("addHousehold", household);
                // DISABLED: in init or expand loop, layer is uncheck when added
                //commit('addExcludedNode', household.id)
                //commit('updateHack')
                dispatch("addLinkFromPersonsToHousehold", household);
                commit("updateHack");
                resolve();
              }),
          )
          .catch(() => {
            commit("unmarkHouseholdLoading", person.current_household_id);
          });
      }
    },

    /**
     *  4) Add an edge for each household member (household -> person)
     *  @param object
     *  @param household
     */
    addLinkFromPersonsToHousehold({ commit, getters, dispatch }, household) {
      let members = getters.getMembersByHousehold(household.id);
      //console.log('add link for', members.length, 'members')
      members.forEach((m) => {
        commit("addLink", {
          from: `${m.person.type}_${m.person.id}`,
          to: `${household.id}`,
          id: `${household.id}-person_${m.person.id}`,
          arrows: "from",
          color: lightBrown,
          font: { color: darkBrown },
          dashes: getHouseholdWidth(m) === 1 ? [0, 4] : false, //edge style:  [dash, gap, dash, gap]
          width: getHouseholdWidth(m),
        });
        if (!getters.isPersonLoaded(m.person.id)) {
          dispatch("addMissingPerson", [m.person, household]);
        }
      });
    },

    /**
     *  5) Fetch AccompanyingCourses for the person
     *  @param object
     *  @param person
     */
    fetchCoursesByPerson({ dispatch }, person) {
      return getCoursesByPerson(person).then(
        (courses) =>
          new Promise((resolve) => {
            dispatch("addCourses", courses);
            resolve();
          }),
      );
    },

    /**
     *  6) Add each distinct course (a person can have multiple courses)
     *  @param object
     *  @param courses
     */
    addCourses({ commit, getters, dispatch }, courses) {
      let currentCourses = courses.filter((c) => c.closingDate === null);
      currentCourses.forEach((course) => {
        //console.log('  isCourseLoaded ?', getters.isCourseLoaded(course.id))
        if (!getters.isCourseLoaded(course.id)) {
          commit("markCourseLoaded", course.id);
          commit("addCourse", course);
          commit("addExcludedNode", course.id); // in init or expand loop, layer is uncheck when added
          dispatch("addLinkFromPersonsToCourse", course);
          commit("updateHack");
        }
      });
    },

    /**
     *  7) Add an edge for each course participation (course <- person)
     *  @param object
     *  @param course
     */
    addLinkFromPersonsToCourse({ commit, getters, dispatch }, course) {
      const participations = getters.getParticipationsByCourse(course.id);
      //console.log('add link for', participations.length, 'participations')
      participations.forEach((p) => {
        //console.log(p.person.id)
        commit("addLink", {
          from: `${p.person.type}_${p.person.id}`,
          to: `${course.id}`,
          id: `accompanying_period_${splitId(course.id, "id")}-person_${p.person.id}`,
          arrows: "from",
          color: lightBlue,
          font: { color: darkBlue },
        });
        if (!getters.isPersonLoaded(p.person.id)) {
          dispatch("addMissingPerson", [p.person, course]);
        }
      });

      return Promise.resolve();
    },

    /**
     *  8) Fetch Relationship
     *  @param object
     *  @param person
     */
    fetchRelationshipByPerson({ dispatch }, person) {
      //console.log('fetchRelationshipByPerson', person)
      getRelationshipsByPerson(person).then(
        (relationships) =>
          new Promise((resolve) => {
            dispatch("addRelationships", relationships);
            resolve();
          }),
      );
    },

    /**
     *  9) Add each distinct relationship
     *  @param object
     *  @param relationships
     */
    addRelationships({ commit, getters, dispatch }, relationships) {
      relationships.forEach((relationship) => {
        //console.log('  isRelationshipLoaded ?', getters.isRelationshipLoaded(relationship.id))
        if (!getters.isRelationshipLoaded(relationship.id)) {
          commit("markRelationshipLoaded", relationship.id);
          commit("addRelationship", relationship);
          dispatch("addLinkFromRelationship", relationship);
          commit("updateHack");
        }
      });
    },

    /**
     *  10) Add an edge for each relationship (person -> person)
     *  @param object
     *  @param relationship
     */
    addLinkFromRelationship({ commit, getters, dispatch }, relationship) {
      //console.log('-> addLink from person', relationship.fromPerson.id, 'to person', relationship.toPerson.id)
      commit("addLink", {
        from: `person_${relationship.fromPerson.id}`,
        to: `person_${relationship.toPerson.id}`,
        id:
          "relationship_" +
          splitId(relationship.id, "id") +
          "-person_" +
          relationship.fromPerson.id +
          "-person_" +
          relationship.toPerson.id,
        arrows: getRelationshipDirection(relationship),
        color: lightGreen,
        font: { color: darkGreen },
        dashes: true,
        label: getRelationshipLabel(relationship),
        title: getRelationshipTitle(relationship),
        relation: relationship.relation,
        reverse: relationship.reverse,
      });
      for (let person of [relationship.fromPerson, relationship.toPerson]) {
        if (!getters.isPersonLoaded(person.id)) {
          dispatch("addMissingPerson", [person, relationship]);
        }
      }
    },

    /**
     *  Add missing person. node is displayed without label (folded).
     *  We stop here and listen on events to unfold person and expand its fetch infos
     *  @param object
     *  @param array
     */
    addMissingPerson({ commit, getters }, [person, parent]) {
      //console.log('! add missing Person', person.id)
      commit("markPersonLoaded", person.id);
      commit("addPerson", [person, { folded: true }]);
      if (getters.isExcludedNode(parent.id)) {
        // in init or expand loop, exclude too missing persons if parent have been excluded
        commit("addExcludedNode", person.id);
      }
      commit("updateHack");
    },

    /**
     * ==================================================================
     * Triggered by a vis-network event when clicking on a Course Node.
     * Each folded node is unfold, then expanded with fetch infos
     * @param object
     * @param course
     */
    unfoldPersonsByCourse({ getters, commit, dispatch }, course) {
      const participations = getters.getParticipationsByCourse(course.id);
      getters.getPersonsGroup(participations).forEach((person) => {
        if (person.folded === true) {
          //console.log('-=. unfold and expand person', person.id)
          commit("unfoldPerson", person);
          dispatch("fetchInfoForPerson", person);
        }
      });
    },

    /**
     * Triggered by a vis-network event when clicking on a Household Node.
     * Each folded node is unfold, then expanded with fetch infos
     * @param object
     * @param household
     */
    unfoldPersonsByHousehold({ getters, commit, dispatch }, household) {
      const members = getters.getMembersByHousehold(household.id);
      getters.getPersonsGroup(members).forEach((person) => {
        if (person.folded === true) {
          //console.log('-=. unfold and expand person', person.id)
          commit("unfoldPerson", person);
          dispatch("fetchInfoForPerson", person);
        }
      });
    },

    /**
     * ==================================================================
     * For an excluded node, add|remove relative persons excluded too
     * @param object
     * @param array (add|remove action, id)
     */
    excludedNode({ getters, commit }, [action, id]) {
      const personGroup = () => {
        switch (splitId(id, "type")) {
          case "accompanying_period":
            return getters.getParticipationsByCourse(id);
          case "household":
            return getters.getMembersByHousehold(id);
          default:
            throw "undefined case with this id";
        }
      };
      let group = getters.getPersonsGroup(personGroup());
      if (action === "add") {
        commit("addExcludedNode", id);
        group.forEach((person) => {
          // countLinks < 2 but parent has just already been added !
          if (
            !getters.isInWhitelist(person.id) &&
            getters.countLinksByNode(person.id) < 1
          ) {
            commit("addExcludedNode", person.id);
          }
        });
      }
      if (action === "remove") {
        commit("removeExcludedNode", id);
        group.forEach((person) => {
          commit("removeExcludedNode", person.id);
        });
      }
      commit("updateHack");
    },
  },
});

export { store };
