"use strict";

// module to control the Tools options (click to edit, to re-geenerate, tp add)

toolsContent.addEventListener("click", function (event) {
  if (customization) return tip("Please exit the customization mode first", false, "error");
  if (!["BUTTON", "I"].includes(event.target.tagName)) return;
  const button = event.target.id;

  // click on open Editor buttons
  if (button === "editHeightmapButton") editHeightmap();
  else if (button === "editBiomesButton") editBiomes();
  else if (button === "editStatesButton") editStates();
  else if (button === "editProvincesButton") editProvinces();
  else if (button === "editDiplomacyButton") editDiplomacy();
  else if (button === "editCulturesButton") editCultures();
  else if (button === "editReligions") editReligions();
  else if (button === "editEmblemButton") openEmblemEditor();
  else if (button === "editNamesBaseButton") editNamesbase();
  else if (button === "editUnitsButton") editUnits();
  else if (button === "editNotesButton") editNotes();
  else if (button === "editZonesButton") editZones();
  else if (button === "overviewChartsButton") overviewCharts();
  else if (button === "overviewBurgsButton") overviewBurgs();
  else if (button === "overviewRoutesButton") overviewRoutes();
  else if (button === "overviewRiversButton") overviewRivers();
  else if (button === "overviewMilitaryButton") overviewMilitary();
  else if (button === "overviewMarkersButton") overviewMarkers();
  else if (button === "overviewCellsButton") viewCellDetails();

  // click on Regenerate buttons
  if (event.target.parentNode.id === "regenerateFeature") {
    const dontAsk = sessionStorage.getItem("regenerateFeatureDontAsk");
    if (dontAsk) return processFeatureRegeneration(event, button);

    alertMessage.innerHTML = /* html */ `Regeneration will remove all the custom changes for the element.<br /><br />Are you sure you want to proceed?`;
    $("#alert").dialog({
      resizable: false,
      title: "Regenerate element",
      buttons: {
        Proceed: function () {
          processFeatureRegeneration(event, button);
          $(this).dialog("close");
        },
        Cancel: function () {
          $(this).dialog("close");
        }
      },
      open: function () {
        const checkbox =
          '<span><input id="dontAsk" class="checkbox" type="checkbox"><label for="dontAsk" class="checkbox-label dontAsk"><i>do not ask again</i></label><span>';
        const pane = this.parentElement.querySelector(".ui-dialog-buttonpane");
        pane.insertAdjacentHTML("afterbegin", checkbox);
      },
      close: function () {
        const box = this.parentElement.querySelector(".checkbox");
        if (box?.checked) sessionStorage.setItem("regenerateFeatureDontAsk", true);
        $(this).dialog("destroy");
      }
    });
  }

  // click on Configure regenerate buttons
  if (button === "configRegenerateMarkers") configMarkersGeneration();

  // click on Add buttons
  if (button === "addLabel") toggleAddLabel();
  else if (button === "addBurgTool") toggleAddBurg();
  else if (button === "addRiver") toggleAddRiver();
  else if (button === "addRoute") createRoute();
  else if (button === "addMarker") toggleAddMarker();
  // click to create a new map buttons
  else if (button === "openSubmapTool") openSubmapTool();
  else if (button === "openTransformTool") openTransformTool();
});

function processFeatureRegeneration(event, button) {
  if (button === "regenerateStateLabels") {
    $("#labels").fadeIn();
    drawStateLabels();
  } else if (button === "regenerateReliefIcons") {
    drawReliefIcons();
    if (!layerIsOn("toggleRelief")) toggleRelief();
  } else if (button === "regenerateRoutes") {
    regenerateRoutes();
    if (!layerIsOn("toggleRoutes")) toggleRoutes();
  } else if (button === "regenerateRivers") regenerateRivers();
  else if (button === "regeneratePopulation") recalculatePopulation();
  else if (button === "regenerateStates") regenerateStates();
  else if (button === "regenerateProvinces") regenerateProvinces();
  else if (button === "regenerateBurgs") regenerateBurgs();
  else if (button === "regenerateEmblems") regenerateEmblems();
  else if (button === "regenerateReligions") regenerateReligions();
  else if (button === "regenerateCultures") regenerateCultures();
  else if (button === "regenerateMilitary") regenerateMilitary();
  else if (button === "regenerateIce") regenerateIce();
  else if (button === "regenerateMarkers") regenerateMarkers();
  else if (button === "regenerateZones") regenerateZones(event);
}

async function openEmblemEditor() {
  let type, id, el;

  const firstState = pack.states.find(s => s.i && !s.removed && s.coa);
  const firstBurg = pack.burgs.find(b => b.i && !b.removed && b.coa);

  if (firstState) {
    type = "state";
    id = `stateCOA${firstState.i}`;
    el = firstState;
  } else if (firstBurg) {
    type = "burg";
    id = `burgCOA${firstBurg.i}`;
    el = firstBurg;
  } else {
    tip("No emblems to edit, please generate states and burgs first", false, "error");
    return;
  }

  await COArenderer.trigger(id, el.coa);
  editEmblem(type, id, el);
}

function regenerateRoutes() {
  const locked = pack.routes.filter(route => route.lock).map((route, index) => ({...route, i: index}));
  Routes.generate(locked);

  routes.selectAll("path").remove();
  if (layerIsOn("toggleRoutes")) drawRoutes();
}

function regenerateRivers() {
  Rivers.generate();
  Rivers.specify();
  Features.defineGroups();
  Lakes.defineNames();
  if (layerIsOn("toggleRivers")) drawRivers();
}

function recalculatePopulation() {
  rankCells();

  pack.burgs.forEach(b => {
    if (!b.i || b.removed || b.lock) return;
    const i = b.cell;

    b.population = rn(Math.max(pack.cells.s[i] / 8 + b.i / 1000 + (i % 100) / 1000, 0.1), 3);
    if (b.capital) b.population = b.population * 1.3; // increase capital population
    if (b.port) b.population = b.population * 1.3; // increase port population
    b.population = rn(b.population * gauss(2, 3, 0.6, 20, 3), 3);
  });

  layerIsOn("togglePopulation") ? drawPopulation() : togglePopulation();
}

function regenerateStates() {
  const newStates = recreateStates();
  if (!newStates) return;

  pack.states = newStates;
  States.expandStates();
  States.normalize();
  States.getPoles();
  States.findNeighbors();
  States.collectStatistics();
  States.assignColors();
  States.generateCampaigns();
  States.generateDiplomacy();
  States.defineStateForms();

  Provinces.generate(true);
  Provinces.getPoles();

  layerIsOn("toggleStates") ? drawStates() : toggleStates();
  layerIsOn("toggleBorders") ? drawBorders() : toggleBorders();
  if (layerIsOn("toggleProvinces")) drawProvinces();

  drawStateLabels();
  Military.generate();
  if (layerIsOn("toggleEmblems")) drawEmblems();

  if (byId("burgsOverviewRefresh")?.offsetParent) burgsOverviewRefresh.click();
  if (byId("statesEditorRefresh")?.offsetParent) statesEditorRefresh.click();
  if (byId("militaryOverviewRefresh")?.offsetParent) militaryOverviewRefresh.click();
}

function recreateStates() {
  const localSeed = generateSeed();
  Math.random = aleaPRNG(localSeed);

  const statesCount = +byId("statesNumber").value;
  if (!statesCount) {
    tip(`<i>States Number</i> option value is zero. No counties are generated`, false, "error");
    return null;
  }

  const validBurgs = pack.burgs.filter(b => b.i && !b.removed);
  if (!validBurgs.length) {
    tip("There are no any burgs to generate states. Please create burgs first", false, "error");
    return null;
  }

  if (validBurgs.length < statesCount) {
    const message = `Not enough burgs to generate ${statesCount} states. Will generate only ${validBurgs.length} states`;
    tip(message, false, "warn");
  }

  const validStates = pack.states.filter(s => s.i && !s.removed);
  const lockedStates = validStates.filter(s => s.lock);
  const lockedStatesIds = lockedStates.map(s => s.i);
  const lockedStatesCapitals = lockedStates.map(s => s.capital);

  if (validStates.length && lockedStates.length === validStates.length) {
    tip("Unable to regenerate as all states are locked", false, "error");
    return null;
  }

  // turn all old capitals into town, except for the capitals of locked states
  for (const burg of validBurgs) {
    if (burg.capital) {
      if (lockedStatesCapitals.includes(burg.i)) continue;
      burg.capital = 0;
      Burgs.changeGroup(burg);
    }
  }

  // remove labels and emblems for non-locked states
  for (const state of pack.states) {
    if (!state.i || state.removed || state.lock) continue;

    // remove state labels
    byId(`stateLabel${state.i}`)?.remove();
    byId(`textPath_stateLabel${state.i}`)?.remove();

    // remove state emblems
    byId(`stateCOA${state.i}`)?.remove();
    document.querySelector(`#stateEmblems > use[data-i="${state.i}"]`)?.remove();

    // remove province data and emblems
    for (const provinceId of state.provinces) {
      byId(`provinceCOA${provinceId}`)?.remove();
      document.querySelector(`#provinceEmblems > use[data-i="${provinceId}"]`)?.remove();
      pack.provinces[provinceId].removed = true;
    }
  }

  unfog();

  // burg local ids sorted by a bit randomized population. Also ignore burgs of a locked state
  const sortedBurgs = validBurgs
    .filter(b => !lockedStatesIds.includes(b.state))
    .map(b => [b, b.population * Math.random()])
    .sort((a, b) => b[1] - a[1])
    .map(b => b[0]);

  const count = Math.min(statesCount, validBurgs.length) + 1; // +1 for neutral
  let spacing = (graphWidth + graphHeight) / 2 / count; // min distance between capitals

  const capitalsTree = d3.quadtree();
  const isTooClose = (x, y, spacing) => Boolean(capitalsTree.find(x, y, spacing));

  const newStates = [{i: 0, name: pack.states[0].name}];

  // restore locked states
  lockedStates.forEach(state => {
    const newId = newStates.length;
    const {x, y} = pack.burgs[state.capital];
    capitalsTree.add([x, y]);

    // update label id reference
    byId(`textPath_stateLabel${state.i}`)?.setAttribute("id", `textPath_stateLabel${newId}`);
    const $label = byId(`stateLabel${state.i}`);
    if ($label) {
      $label.setAttribute("id", `stateLabel${newId}`);
      const $textPath = $label.querySelector("textPath");
      if ($textPath) {
        $textPath.removeAttribute("href");
        $textPath.setAttribute("href", `#textPath_stateLabel${newId}`);
      }
    }

    // update emblem id reference
    byId(`stateCOA${state.i}`)?.setAttribute("id", `stateCOA${newId}`);
    document.querySelector(`#stateEmblems > use[data-i="${state.i}"]`)?.setAttribute("data-i", newId);

    state.provinces.forEach(provinceId => {
      if (!pack.provinces[provinceId]) return;
      pack.provinces[provinceId].state = newId;
    });

    state.i = newId;
    newStates.push(state);
  });

  for (const i of pack.cells.i) {
    const stateId = pack.cells.state[i];
    const lockedStateIndex = lockedStatesIds.indexOf(stateId) + 1;
    // lockedStateIndex is an index of locked state or 0 if state is not locked
    pack.cells.state[i] = lockedStateIndex;
  }

  for (let i = newStates.length; i < count; i++) {
    let capital = null;

    for (const burg of sortedBurgs) {
      const {x, y} = burg;
      if (!isTooClose(x, y, spacing)) {
        burg.capital = 1;
        capital = burg;
        capitalsTree.add([x, y]);
        Burgs.changeGroup(capital);
        break;
      }

      spacing = Math.max(spacing - 1, 1);
    }

    // all burgs are too close, should not happen in normal conditions
    if (!capital) break;

    // create new state
    const culture = capital.culture;
    const basename =
      capital.name.length < 9 && capital.cell % 5 === 0 ? capital.name : Names.getCulture(culture, 3, 6, "", 0);
    const name = Names.getState(basename, culture);
    const nomadic = [1, 2, 3, 4].includes(pack.cells.biome[capital.cell]);
    const type = nomadic
      ? "Nomadic"
      : pack.cultures[culture].type === "Nomadic"
      ? "Generic"
      : pack.cultures[culture].type;
    const expansionism = rn(Math.random() * byId("sizeVariety").value + 1, 1);

    const cultureType = pack.cultures[culture].type;
    const coa = COA.generate(capital.coa, 0.3, null, cultureType);
    coa.shield = capital.coa.shield;

    newStates.push({i, name, type, capital: capital.i, center: capital.cell, culture, expansionism, coa});
  }

  return newStates;
}

function regenerateProvinces() {
  unfog();

  Provinces.generate(true, true);
  Provinces.getPoles();

  if (layerIsOn("toggleBorders")) drawBorders();
  layerIsOn("toggleProvinces") ? drawProvinces() : toggleProvinces();

  // remove emblems
  document.querySelectorAll("[id^=provinceCOA]").forEach(el => el.remove());
  emblems.selectAll("use").remove();
  if (layerIsOn("toggleEmblems")) drawEmblems();
  refreshAllEditors();
}

function regenerateBurgs() {
  const {cells, features, burgs, states, provinces} = pack;

  rankCells();

  // remove notes for unlocked burgs
  notes = notes.filter(note => {
    if (note.id.startsWith("burg")) {
      const burgId = +note.id.slice(4);
      return burgs[burgId]?.lock;
    }
    return true;
  });

  const newBurgs = [0]; // new burgs array
  const burgsTree = d3.quadtree();

  cells.burg = new Uint16Array(cells.i.length); // clear cells burg data
  states.filter(s => s.i).forEach(s => (s.capital = 0)); // clear state capitals
  provinces.filter(p => p.i).forEach(p => (p.burg = 0)); // clear province capitals

  // readd locked burgs
  const lockedburgs = burgs.filter(burg => burg.i && !burg.removed && burg.lock);
  for (let j = 0; j < lockedburgs.length; j++) {
    const lockedBurg = lockedburgs[j];
    const newId = newBurgs.length;

    const noteIndex = notes.findIndex(note => note.id === `burg${lockedBurg.i}`);
    if (noteIndex !== -1) notes[noteIndex].id = `burg${newId}`;

    lockedBurg.i = newId;
    newBurgs.push(lockedBurg);

    burgsTree.add([lockedBurg.x, lockedBurg.y]);
    cells.burg[lockedBurg.cell] = newId;

    if (lockedBurg.capital) {
      const stateId = lockedBurg.state;
      states[stateId].capital = newId;
      states[stateId].center = lockedBurg.cell;
    }
  }

  const score = new Int16Array(cells.s.map(s => s * Math.random())); // cell score for capitals placement
  const sorted = cells.i.filter(i => score[i] > 0 && cells.culture[i]).sort((a, b) => score[b] - score[a]); // filtered and sorted array of indexes
  const existingStatesCount = states.filter(s => s.i && !s.removed).length;
  const burgsCount =
    (manorsInput.value === "1000" ? rn(sorted.length / 5 / (grid.points.length / 10000) ** 0.8) : +manorsInput.value) +
    existingStatesCount;
  const spacing = (graphWidth + graphHeight) / 150 / (burgsCount ** 0.7 / 66); // base min distance between town

  for (let i = 0; i < sorted.length && newBurgs.length < burgsCount; i++) {
    const id = newBurgs.length;
    const cell = sorted[i];
    const [x, y] = cells.p[cell];

    const s = spacing * gauss(1, 0.3, 0.2, 2, 2); // randomize to make the placement not uniform
    if (burgsTree.find(x, y, s) !== undefined) continue; // to close to existing burg

    const stateId = cells.state[cell];
    const capital = stateId && !states[stateId].capital; // if state doesn't have capital, make this burg a capital, no capital for neutral lands
    if (capital) {
      states[stateId].capital = id;
      states[stateId].center = cell;
    }

    const culture = cells.culture[cell];
    const name = Names.getCulture(culture);
    newBurgs.push({cell, x, y, state: stateId, i: id, culture, name, capital, feature: cells.f[cell]});
    burgsTree.add([x, y]);
    cells.burg[cell] = id;
  }

  pack.burgs = newBurgs; // assign new burgs array
  Burgs.shift();

  // add a capital at former place for states without added capitals
  states
    .filter(s => s.i && !s.removed && !s.capital)
    .forEach(s => {
      const [x, y] = cells.p[s.center];
      const burgId = Burgs.add([x, y]);
      s.capital = burgId;
      s.center = pack.burgs[burgId].cell;

      const burg = pack.burgs[burgId];
      burg.state = s.i;
      burg.capital = 1;
      Burgs.changeGroup(burg);
    });

  Burgs.specify();
  regenerateRoutes();

  drawBurgIcons();
  drawBurgLabels();

  // remove emblems
  document.querySelectorAll("[id^=burgCOA]").forEach(el => el.remove());
  emblems.selectAll("use").remove();
  if (layerIsOn("toggleEmblems")) drawEmblems();

  if (byId("burgsOverviewRefresh")?.offsetParent) burgsOverviewRefresh.click();
  if (byId("statesEditorRefresh")?.offsetParent) statesEditorRefresh.click();
}

function regenerateEmblems() {
  // remove old emblems
  document.querySelectorAll("[id^=stateCOA]").forEach(el => el.remove());
  document.querySelectorAll("[id^=provinceCOA]").forEach(el => el.remove());
  document.querySelectorAll("[id^=burgCOA]").forEach(el => el.remove());
  emblems.selectAll("use").remove();

  // generate new emblems
  pack.states.forEach(state => {
    if (!state.i || state.removed) return;
    const cultureType = pack.cultures[state.culture].type;
    state.coa = COA.generate(null, null, null, cultureType);
    state.coa.shield = COA.getShield(state.culture, null);
  });

  pack.burgs.forEach(burg => {
    if (!burg.i || burg.removed) return;
    const state = pack.states[burg.state];

    let kinship = state ? 0.25 : 0;
    if (burg.capital) kinship += 0.1;
    else if (burg.port) kinship -= 0.1;
    if (state && burg.culture !== state.culture) kinship -= 0.25;
    burg.coa = COA.generate(state ? state.coa : null, kinship, null, burg.type);
    burg.coa.shield = COA.getShield(burg.culture, state ? burg.state : 0);
  });

  pack.provinces.forEach(province => {
    if (!province.i || province.removed) return;
    const parent = province.burg ? pack.burgs[province.burg] : pack.states[province.state];

    let dominion = false;
    if (!province.burg) {
      dominion = P(0.2);
      if (province.formName === "Colony") dominion = P(0.95);
      else if (province.formName === "Island") dominion = P(0.6);
      else if (province.formName === "Islands") dominion = P(0.5);
      else if (province.formName === "Territory") dominion = P(0.4);
      else if (province.formName === "Land") dominion = P(0.3);
    }

    const nameByBurg = province.burg && province.name.slice(0, 3) === parent.name.slice(0, 3);
    const kinship = dominion ? 0 : nameByBurg ? 0.8 : 0.4;
    const culture = pack.cells.culture[province.center];
    const type = Burgs.getType(province.center, parent.port);
    province.coa = COA.generate(parent.coa, kinship, dominion, type);
    province.coa.shield = COA.getShield(culture, province.state);
  });

  layerIsOn("toggleEmblems") ? drawEmblems() : toggleEmblems();
}

function regenerateReligions() {
  Religions.generate();

  layerIsOn("toggleReligions") ? drawReligions() : toggleReligions();
  refreshAllEditors();
}

function regenerateCultures() {
  Cultures.generate();
  Cultures.expand();

  // update culture for states
  pack.states = pack.states.map(state => {
    if (!state.i || state.removed) return state;
    return {...state, culture: pack.cells.culture[state.center]};
  });

  // update culture for burgs
  pack.burgs = pack.burgs.map(burg => {
    if (!burg.i || burg.removed) return burg;
    return {...burg, culture: pack.cells.culture[burg.cell]};
  });

  // update culture for religions
  pack.religions = pack.religions.map(religion => {
    if (!religion.i || religion.removed) return religion;
    return {...religion, culture: pack.cells.culture[religion.center]};
  });

  layerIsOn("toggleCultures") ? drawCultures() : toggleCultures();
  refreshAllEditors();
}

function regenerateMilitary() {
  Military.generate();
  if (layerIsOn("toggleMilitary")) drawMilitary();
  else toggleMilitary();

  if (byId("militaryOverviewRefresh").offsetParent) militaryOverviewRefresh.click();
}

function regenerateIce() {
  if (!layerIsOn("toggleIce")) toggleIce();
  Ice.generate();
  drawIce();
}

function regenerateMarkers() {
  Markers.regenerate();
  turnButtonOn("toggleMarkers");
  drawMarkers();
  if (byId("markersOverviewRefresh").offsetParent) markersOverviewRefresh.click();
}

function regenerateZones(event) {
  if (isCtrlClick(event))
    prompt("Please provide zones number multiplier", {default: 1, step: 0.01, min: 0, max: 100}, v =>
      addNumberOfZones(v)
    );
  else addNumberOfZones(gauss(1, 0.5, 0.6, 5, 2));

  function addNumberOfZones(number) {
    Zones.generate(number);
    if (byId("zonesEditorRefresh").offsetParent) zonesEditorRefresh.click();
    if (layerIsOn("toggleZones")) drawZones();
  }
}

function unpressClickToAddButton() {
  addFeature.querySelectorAll("button.pressed").forEach(b => b.classList.remove("pressed"));
  restoreDefaultEvents();
  clearMainTip();
}

function toggleAddLabel() {
  const pressed = byId("addLabel").classList.contains("pressed");
  if (pressed) {
    unpressClickToAddButton();
    return;
  }

  addFeature.querySelectorAll("button.pressed").forEach(b => b.classList.remove("pressed"));
  addLabel.classList.add("pressed");
  closeDialogs(".stable");
  viewbox.style("cursor", "crosshair").on("click", addLabelOnClick);
  tip("Click on map to place label. Hold Shift to add multiple", true);
  if (!layerIsOn("toggleLabels")) toggleLabels();
}

function addLabelOnClick() {
  const point = d3.mouse(this);

  // get culture in clicked point to generate a name
  const cell = findCell(point[0], point[1]);
  const culture = pack.cells.culture[cell];
  const name = Names.getCulture(culture);
  const id = getNextId("label");

  // use most recently selected label group
  const lastSelected = labelGroupSelect.value;
  const groupId = ["", "states", "burgLabels"].includes(lastSelected) ? "#addedLabels" : "#" + lastSelected;

  let group = labels.select(groupId);
  if (!group.size())
    group = labels
      .append("g")
      .attr("id", "addedLabels")
      .attr("fill", "#3e3e4b")
      .attr("opacity", 1)
      .attr("stroke", "#3a3a3a")
      .attr("stroke-width", 0)
      .attr("font-family", "Almendra SC")
      .attr("font-size", 18)
      .attr("data-size", 18)
      .attr("filter", null);

  const example = group.append("text").attr("x", 0).attr("y", 0).text(name);
  const width = example.node().getBBox().width;
  example.remove();

  group.classed("hidden", false);
  group
    .append("text")
    .attr("text-rendering", "optimizeSpeed")
    .attr("id", id)
    .append("textPath")
    .attr("text-rendering", "optimizeSpeed")
    .attr("xlink:href", "#textPath_" + id)
    .attr("startOffset", "50%")
    .attr("font-size", "100%")
    .append("tspan")
    .attr("x", 0)
    .text(name);

  defs
    .select("#textPaths")
    .append("path")
    .attr("id", "textPath_" + id)
    .attr("d", `M${point[0] - width},${point[1]} h${width * 2}`);

  if (d3.event.shiftKey === false) unpressClickToAddButton();
}

function toggleAddBurg() {
  unpressClickToAddButton();
  byId("addBurgTool").classList.add("pressed");
  overviewBurgs();
  byId("addNewBurg").click();
}

function toggleAddRiver() {
  const pressed = byId("addRiver").classList.contains("pressed");
  if (pressed) {
    unpressClickToAddButton();
    byId("addNewRiver").classList.remove("pressed");
    return;
  }

  addFeature.querySelectorAll("button.pressed").forEach(b => b.classList.remove("pressed"));
  addRiver.classList.add("pressed");
  byId("addNewRiver").classList.add("pressed");
  closeDialogs(".stable");
  viewbox.style("cursor", "crosshair").on("click", addRiverOnClick);
  tip("Click on map to place new river or extend an existing one. Hold Shift to place multiple rivers", true, "warn");
  if (!layerIsOn("toggleRivers")) toggleRivers();
}

function addRiverOnClick() {
  const {cells, rivers} = pack;
  let i = findCell(...d3.mouse(this));

  if (cells.r[i]) return tip("There is already a river here", false, "error");
  if (cells.h[i] < 20) return tip("Cannot create river in water cell", false, "error");
  if (cells.b[i]) return;

  const riverCells = [];
  let riverId = Rivers.getNextId(rivers);
  let parent = riverId;

  const initialFlux = grid.cells.prec[cells.g[i]];
  cells.fl[i] = initialFlux;

  const h = Rivers.alterHeights();
  Rivers.resolveDepressions(h);

  while (i) {
    cells.r[i] = riverId;
    riverCells.push(i);

    const min = cells.c[i].sort((a, b) => h[a] - h[b])[0]; // downhill cell
    if (h[i] <= h[min]) return tip(`Cell ${i} is depressed, river cannot flow further`, false, "error");

    // pour to water body
    if (h[min] < 20) {
      riverCells.push(min);

      const feature = pack.features[cells.f[min]];
      if (feature.type === "lake") {
        if (feature.outlet) parent = feature.outlet;
        feature.inlets ? feature.inlets.push(riverId) : (feature.inlets = [riverId]);
      }
      break;
    }

    // pour outside of map from border cell
    if (cells.b[min]) {
      cells.fl[min] += cells.fl[i];
      riverCells.push(-1);
      break;
    }

    // continue propagation if min cell has no river
    if (!cells.r[min]) {
      cells.fl[min] += cells.fl[i];
      i = min;
      continue;
    }

    // handle case when lowest cell already has a river
    const oldRiverId = cells.r[min];
    const oldRiver = rivers.find(river => river.i === oldRiverId);
    const oldRiverCells = oldRiver?.cells || cells.i.filter(i => cells.r[i] === oldRiverId);
    const oldRiverCellsUpper = oldRiverCells.filter(i => h[i] > h[min]);

    // create new river as a tributary
    if (riverCells.length <= oldRiverCellsUpper.length) {
      cells.conf[min] += cells.fl[i];
      riverCells.push(min);
      parent = oldRiverId;
      break;
    }

    // continue old river
    byId("river" + oldRiverId)?.remove();
    riverCells.forEach(i => (cells.r[i] = oldRiverId));
    oldRiverCells.forEach(cell => {
      if (h[cell] > h[min]) {
        cells.r[cell] = 0;
        cells.fl[cell] = grid.cells.prec[cells.g[cell]];
      } else {
        riverCells.push(cell);
        cells.fl[cell] += cells.fl[i];
      }
    });
    riverId = oldRiverId;

    break;
  }

  const river = rivers.find(r => r.i === riverId);

  const source = riverCells[0];
  const mouth = riverCells[riverCells.length - 2];

  const defaultWidthFactor = rn(1 / (pointsInput.dataset.cells / 10000) ** 0.25, 2);
  const widthFactor =
    river?.widthFactor || (!parent || parent === riverId ? defaultWidthFactor * 1.2 : defaultWidthFactor);
  const sourceWidth = river?.sourceWidth || Rivers.getSourceWidth(cells.fl[source]);
  const meanderedPoints = Rivers.addMeandering(riverCells);

  const discharge = cells.fl[mouth]; // m3 in second
  const length = Rivers.getApproximateLength(meanderedPoints);
  const width = Rivers.getWidth(
    Rivers.getOffset({
      flux: discharge,
      pointIndex: meanderedPoints.length,
      widthFactor,
      startingWidth: sourceWidth
    })
  );

  if (river) {
    river.source = source;
    river.length = length;
    river.discharge = discharge;
    river.width = width;
    river.cells = riverCells;
  } else {
    const basin = Rivers.getBasin(parent);
    const name = Rivers.getName(mouth);
    const type = Rivers.getType({i: riverId, length, parent});

    rivers.push({
      i: riverId,
      source,
      mouth,
      discharge,
      length,
      width,
      widthFactor,
      sourceWidth,
      parent,
      cells: riverCells,
      basin,
      name,
      type
    });
  }

  // render river
  const path = Rivers.getRiverPath(meanderedPoints, widthFactor, sourceWidth);
  const id = "river" + riverId;
  const riversG = viewbox.select("#rivers");
  riversG.append("path").attr("id", id).attr("d", path);

  if (d3.event.shiftKey === false) {
    Lakes.cleanupLakeData();
    unpressClickToAddButton();
    byId("addNewRiver").classList.remove("pressed");
    if (addNewRiver.offsetParent) riversOverviewRefresh.click();
  }
}

function toggleAddMarker() {
  const pressed = byId("addMarker")?.classList.contains("pressed");
  if (pressed) {
    unpressClickToAddButton();
    return;
  }

  addFeature.querySelectorAll("button.pressed").forEach(b => b.classList.remove("pressed"));
  addMarker.classList.add("pressed");
  markersAddFromOverview.classList.add("pressed");

  viewbox.style("cursor", "crosshair").on("click", addMarkerOnClick);
  tip("Click on map to add a marker. Hold Shift to add multiple", true);
  if (!layerIsOn("toggleMarkers")) toggleMarkers();
}

function addMarkerOnClick() {
  const {markers} = pack;
  const point = d3.mouse(this);
  const x = rn(point[0], 2);
  const y = rn(point[1], 2);

  // Find the current cell
  const cell = findCell(point[0], point[1]);

  // Find the currently selected marker to use as a base
  const isMarkerSelected = markers.length && elSelected?.node()?.parentElement?.id === "markers";
  const selectedMarker = isMarkerSelected ? markers.find(marker => marker.i === +elSelected.attr("id").slice(6)) : null;

  const selectedType = byId("addedMarkerType").value;
  const selectedConfig = Markers.getConfig().find(({type}) => type === selectedType);

  const baseMarker = selectedMarker || selectedConfig || {icon: "❓"};
  const marker = Markers.add({...baseMarker, x, y, cell});

  if (selectedConfig && selectedConfig.add) {
    selectedConfig.add("marker" + marker.i, cell);
  }

  const markersElement = byId("markers");
  const rescale = +markersElement.getAttribute("rescale");
  markersElement.insertAdjacentHTML("beforeend", drawMarker(marker, rescale));

  if (d3.event.shiftKey === false) {
    byId("markerAdd").classList.remove("pressed");
    byId("markersAddFromOverview").classList.remove("pressed");
    unpressClickToAddButton();
  }
}

function configMarkersGeneration() {
  drawConfigTable();

  function drawConfigTable() {
    const config = Markers.getConfig();

    const headers = /* html */ `<thead style='font-weight:bold'><tr>
      <td data-tip="Marker type name">Type</td>
      <td data-tip="Marker icon">Icon</td>
      <td data-tip="Marker number multiplier">Multiplier</td>
      <td data-tip="Number of markers of that type on the current map">Number</td>
    </tr></thead>`;

    const lines = config.map(({type, icon, multiplier}) => {
      const isExternal = icon.startsWith("http") || icon.startsWith("data:image");

      return /* html */ `<tr>
        <td><input class="type" value="${type}" /></td>
        <td style="position: relative">
          <img class="image" src="${isExternal ? icon : ""}" ${
        isExternal ? "" : "hidden"
      } style="width:1.2em; height:1.2em; vertical-align: middle;">
          <span class="emoji" style="font-size:1.2em">${isExternal ? "" : icon}</span>
          <button class="changeIcon icon-pencil"></button>
        </td>
        <td><input class="multiplier" type="number" min="0" max="100" step="0.1" value="${multiplier}" /></td>
        <td style="text-align:center">${pack.markers.filter(marker => marker.type === type).length}</td>
      </tr>`;
    });

    const table = `<table class="table">${headers}<tbody>${lines.join("")}</tbody></table>`;
    alertMessage.innerHTML = table;

    alertMessage.querySelectorAll("button.changeIcon").forEach(selectIconButton => {
      selectIconButton.addEventListener("click", function () {
        const image = this.parentElement.querySelector(".image");
        const emoji = this.parentElement.querySelector(".emoji");
        const icon = image.getAttribute("src") || emoji.textContent;

        selectIcon(icon, value => {
          const isExternal = value.startsWith("http") || value.startsWith("data:image");
          image.setAttribute("src", isExternal ? value : "");
          image.hidden = !isExternal;
          emoji.textContent = isExternal ? "" : value;
        });
      });
    });
  }

  const applyChanges = () => {
    const rows = alertMessage.querySelectorAll("tbody > tr");
    const rowsData = Array.from(rows).map(row => {
      const type = row.querySelector(".type").value;

      const image = row.querySelector(".image");
      const emoji = row.querySelector(".emoji");
      const icon = image.getAttribute("src") || emoji.textContent;

      const multiplier = parseFloat(row.querySelector(".multiplier").value);
      return {type, icon, multiplier};
    });

    const config = Markers.getConfig();
    const newConfig = config.map((markerType, index) => {
      const {type, icon, multiplier} = rowsData[index];
      return {...markerType, type, icon, multiplier};
    });

    Markers.setConfig(newConfig);
  };

  $("#alert").dialog({
    resizable: false,
    title: "Markers generation settings",
    position: {my: "left top", at: "left+10 top+10", of: "svg", collision: "fit"},
    buttons: {
      Regenerate: () => {
        applyChanges();
        regenerateMarkers();
        drawConfigTable();
      },
      Close: function () {
        $(this).dialog("close");
      }
    },
    open: function () {
      const buttons = $(this).dialog("widget").find(".ui-dialog-buttonset > button");
      buttons[0].addEventListener("mousemove", () => tip("Apply changes and regenerate markers"));
      buttons[1].addEventListener("mousemove", () => tip("Close the window"));
    },
    close: function () {
      $(this).dialog("destroy");
    }
  });
}

function viewCellDetails() {
  $("#cellInfo").dialog({
    resizable: false,
    width: "22em",
    title: "Cell Details",
    position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}
  });
}

async function overviewCharts() {
  const Overview = await import("../dynamic/overview/charts-overview.js?v=1.99.00");
  Overview.open();
}
