<script setup lang="ts">
import Slider from '@/components/library/Slider.vue';
import Add from "@material-design-icons/svg/filled/add.svg";
import Remove from "@material-design-icons/svg/filled/remove.svg";
import { clamp } from '@vueuse/core';
import { computed } from 'vue';
import ToolButtonSmall from './ToolButtonSmall.vue';

const {
  orientation = "horizontal",
  defaultZoom = undefined,
  extended = false,
} = defineProps<{
  orientation?: "horizontal" | "vertical",
  defaultZoom?: number,
  extended?: boolean,
}>();

/** Zoom factor from 1 (fit content) to about 10 (content takes up 10x more space than the viewport).
 * If extended is set, lower bound will be less than 1 (about 0.5) and upper bound is about 20x.
 */
const zoom = defineModel<number>("zoom", { required: true });

const zoomStepButtons = 10;
const zoomStepSlider = 1;
/* 0..100 or if extended is set -20..100  */
const zoomMin = computed(() => extended ? -2 * zoomStepButtons : 0);
const zoomMax = computed(() => extended ? 200 : 100);

const scale = 16;
// dirty hack because no exp growth: after extended threshold scale is more steep
const scale2 = 8;
const scaleThreshold = 100;
const zoomThreshold = 1 + scaleThreshold / scale;

// zoom: external level 0.5 .. 7.25
// slider value: interval 0 .. 100 or -20 .. 100
function toSliderValue(zoom: number): number {
  if (zoom > zoomThreshold) {
    return toSliderValue(zoomThreshold) + (zoom - zoomThreshold) * scale2;
  }
  if (zoom >= 1) {
    return (zoom - 1) * scale;
  } else {
    return -2 * zoomMin.value * (zoom - 1);
  }
}

function fromSliderValue(value: number): number {
  // dirty hack because no exp growth
  if (value > scaleThreshold) {
    return fromSliderValue(scaleThreshold) + (value - scaleThreshold) / scale2;
  }
  if (value >= 0) {
    return 1 + value / scale;
  } else {
    return 1 - value / (2 * zoomMin.value);
  }
}

const defaultValue = computed(() => defaultZoom === undefined ? undefined : toSliderValue(defaultZoom));

// Internal integer representation that avoids floating point errors.
const zoomSliderValue = computed<number>({
  get() {
    return Math.round(toSliderValue(zoom.value));
  },
  set(value) {
    value = clamp(Math.round(value), zoomMin.value, zoomMax.value);
    zoom.value = fromSliderValue(value);
  },
});

function onButton(direction: number): void {
  let val = zoomSliderValue.value - zoomMin.value;
  if (val % zoomStepButtons !== 0) {
    // go to the nearest full step up or down depending on the direction
    val = ((direction > 0) ? Math.ceil : Math.floor)(val / zoomStepButtons);
    zoomSliderValue.value = val * zoomStepButtons + zoomMin.value;
  } else {
    zoomSliderValue.value += direction * zoomStepButtons;
  }
}
</script>
<template>
  <!-- for some reason min-width does not propagate up from Slider -->
  <div class="tw:px-2 tw:flex tw:items-center tw:gap-2"
    :class="orientation == 'vertical' ? 'tw:flex-col' : 'tw:flex-row'">
    <ToolButtonSmall :icon="Remove" title="Zoom Out" @click="onButton(-1)" :disabled="zoomSliderValue <= zoomMin" />
    <Slider :min="zoomMin" :max="zoomMax" :step="zoomStepSlider" v-model.number="zoomSliderValue" :orientation
      :defaultValue />
    <ToolButtonSmall :icon="Add" title="Zoom In" @click="onButton(+1)" :disabled="zoomSliderValue >= zoomMax" />
  </div>
</template>
<style scoped></style>
