<template>
  <builder-panel :order="order" :builder="builder" :build-tranches="buildTranches"
                 :adjust-shapes="adjustShapes" :delete-shapes="deleteShapes">
    <div style="min-width: 3em; font-size: larger" :style="colorStyle" class="align-self-start ml-2 pt-3">{{ name }}
    </div>
    <div>
      <v-text-field type="number" v-model="rungs"
                    density="compact" hide-details class="mx-1 my-2" variant="outlined"
                    label="Rungs"
                    :color="color" :base-color="color" min="1"
                    :disabled="valueA===null"
                    style="width: 4.5em;"
      />
    </div>

    <slot/>

    <div v-if="co.drawing" class="d-flex align-center pl-3">
      <v-icon icon="mdi-chat-alert-outline" color="grey" class="mr-1"/>
      Click the chart!
    </div>
    <div v-if="rungs>1" class="mx-2 d-flex align-center">
      <v-slider v-if="rungs>1" direction="vertical" min="-100" max="100" v-model="skew100"
                class="no-slider-bg ml-2 mr-4" hide-details/>
      <v-text-field type="number" v-model="skew100" min="-100" max="100"
                    density="compact" hide-details variant="outlined" label="Skew" step="5"
                    :color="color" :base-color="color">
        <template v-slot:prepend>
          <v-btn icon="mdi-scale-balance" variant="plain" @click="builder.skew=0" :color="color"/>
        </template>
      </v-text-field>
    </div>
  </builder-panel>
</template>

<script setup>
import BuilderPanel from "@/components/chart/BuilderPanel.vue";
import {useOrderStore} from "@/store/store.js";
import {allocationText, deleteBuilder, linearWeights, useChartOrderStore, weightColors} from "@/orderbuild.js";
import {useTheme} from "vuetify";
import {linspace, sideColor} from "@/misc.js";
import {computed, onUnmounted, watchEffect} from "vue";
import Color from "color";
import {cancelDrawing} from "@/charts/chart.js";

const os = useOrderStore()
const co = useChartOrderStore()
const theme = useTheme().current
const valueA = defineModel('valueA')
const valueB = defineModel('valueB')
const props = defineProps({
  name: String,
  order: Object,
  builder: Object,
  buildTranches: Function,
  stdWidth: Number,
  shape: Function,  // shape() -> Shape
  mode: { type: Number, default: 0 }, // rung addition mode: 0 = split, 1 = extend
  getModelValue: Function,  // getModelValue(model) -> value
  setModelValue: Function,  // setModelValue(model,value) -> void
  setValues: Function,  // setValues(values:Array) -> void
  setWeights: Function,  // setValues(values:Array) -> void
})

const flipped = computed(() => valueA.value !== null && valueB.value !== null && valueA.value > valueB.value)
const flippedSign = computed(()=>flipped.value?-1:1)

const skew100 = computed( {
  get() {return flippedSign.value*props.builder.skew*100},
  set(v) {props.builder.skew = flippedSign.value*v/100; }
} )

// validity checks
watchEffect(()=>{
  const rungs = props.builder.rungs
  const prev = props.builder.valid
  props.builder.valid = rungs >= 1 && valueA.value && (rungs < 2 || valueB.value)
  console.log('valid?', prev, props.builder.valid, rungs, valueA.value, valueB.value)
})


const rungs = computed({
  get() {
    return props.builder.rungs
  },
  set(r) {
    if (!r) {
      props.builder.rungs = 1
      return
    }
    r = Number(r)
    props.builder.rungs = r
    const b = valueB.value
    // console.log('set rungs', r, valueA.value, b)
    if ( r > 0 && b === null ) {
      // convert single shape to a range
      if (props.mode===0) {
        const width = props.stdWidth
        const mid = valueA.value
        valueA.value = mid - width/2
        valueB.value = mid + width/2
      }
      else if (props.mode===1 ) {
        valueB.value = valueA.value + props.stdWidth
      }
      else
        throw Error(`Unknown rung mode ${props.mode}`)
    }
    else if ( r === 1 && b !== null ) {
      // convert from a range to a single shape
      if (props.mode===0)
        valueA.value = (valueA.value + b) / 2
      valueB.value = null
    }
    else {
      // from multi to multi
      if (props.mode===1)
        valueB.value = valueA.value + props.stdWidth * (r-1)
    }
  }
})


const values = computed(()=>{
  let a = valueA.value
  let b = valueB.value
  const r = props.builder.rungs
  let result
  if ( a===null || !r )
    result = []  // no data
  else if (r===1)
    result = [a]  // single shape
  else
    result = linspace(a, b, r)  // linear spacing
  props.setValues(result)
  return result;
})


const weights = computed(() => {
  // const skew = flipped.value ? -props.builder.skew : props.builder.skew
  const ws = linearWeights(props.builder.rungs, -props.builder.skew)
  if (props.setWeights)
    props.setWeights(ws)
  return ws
})


const amountSymbol = computed(()=>props.order.amountIsTokenA ? co.selectedSymbol.base.s : co.selectedSymbol.quote.s )


// colors
function computeDefaultColor() {
  const index = props.order.builders.indexOf(props.builder)
  return sideColor(props.order.buy, index)
}
const defaultColor = computeDefaultColor()
const color = computed({
  get() {return props.builder.color},
  set(v) {
    const maxLightness = 60
    // noinspection JSUnresolvedReference
    const c = new Color(v).hsl()
    props.builder.color = c.saturation <= maxLightness ? v : c.lightness(maxLightness).rgb().string()
  }
})
const colorStyle = computed(() => {
  return {'color': color.value}
})


//
// SHAPE MANAGEMENT
//

// we keep two special control shapes as the edges of the range, with deletable shapes in-between

function createShape(value, model, onModel, onDelete) {
  props.setModelValue(model, value)
  return new props.shape(model, onModel, onDelete)  // props.shape is the constructor function
}


function translateOnDrag(shape) {
  const oldOnPoints = shape.onPoints
  shape.onPoints = function (points) {
    if (!this.beingDragged()) {
      oldOnPoints.call(this, points)
      return
    }
    const prev = props.getModelValue(this.model)
    oldOnPoints.call(this, points)
    const cur = props.getModelValue(this.model)
    const delta = cur - prev
    if (delta !== 0) {
      valueA.value += delta
      valueB.value += delta
    }
  }
}


const shapeA = createShape(valueA.value, {color: defaultColor},
    function (model) {
      const value = props.getModelValue(model);
      if (value!==null && value!==undefined)
        valueA.value = value;
      if (model.color)
        color.value = model.color
    },
    deleteSelf)

if (props.mode===1)
  translateOnDrag(shapeA)

const shapeB = createShape(valueB.value, {color:defaultColor},
    function (model) {
      const value = props.getModelValue(model);
      if (value!==null && value!==undefined)
        valueB.value = value;
      if (model.color)
        color.value = model.color
    },
    deleteSelf)

function interiorOnModel(model) {
  if (model.color)
    color.value = model.color
}

let interiorShapes = []

function createInteriorShape(index) {
  const shape = new props.shape(makeModel(index), interiorOnModel, deleteSelf)
  translateOnDrag(shape)
  interiorShapes.push(shape)
}


function removeInteriorShape() {
  if (interiorShapes.length) {
    const shape = interiorShapes.pop()
    shape.delete()
  }
}

function makeModel(index) {
  const ws = weights.value
  const alloc = props.builder.allocation * ws[index];
  const result = {
    color: color.value,
    allocation: alloc,
    maxAllocation: Math.max(...weights.value),
    amount: props.order.amount * alloc,
    amountSymbol: amountSymbol.value,
  }
  props.setModelValue(result, values.value[index])
  return result
}

function adjustShapes() {
  // this is where all the shapes are created or adjusted
  // console.log('adjustShapes()', valueA.value, valueB.value)
  const vs = values.value
  if (vs.length)
      cancelDrawing()
  // shape properties
  if( vs.length === 0 ) {
    shapeA.delete()
    shapeB.delete()
    for( const shape of interiorShapes )
      shape.delete()
    interiorShapes = []
  }
  else if (vs.length === 1) {
    //
    // SINGLE SHAPE
    //
    shapeA.setModel(makeModel(0))
    shapeB.delete()
    if (interiorShapes.length) {
      for( const shape of interiorShapes )
        shape.delete()
      interiorShapes = []
    }
  }
  else {
    //
    // VALUE RANGE
    //
    shapeA.setModel(makeModel(0))
    shapeB.setModel(makeModel(vs.length-1))
    const numInterior = Math.max(0,vs.length-2);
    // trim excess interior shapes
    while( interiorShapes.length > numInterior )
      removeInteriorShape()
    // adjust the interior shape values and/or add shapes
    for( let i=1; i<vs.length-1; i++ ) {
      if (i-1 === interiorShapes.length)
        createInteriorShape(i)
      else
        interiorShapes[i-1].setModel(makeModel(i))
    }
  }
  return ''
}

function deleteSelf() {
  deleteBuilder(props.order, props.builder);
}

function deleteShapes() {
  shapeA.delete()
  shapeB.delete()
  for (const shape of interiorShapes)
    shape.delete()
  interiorShapes = []
}

onUnmounted(deleteShapes)

if (!valueA.value)
  shapeA.createOrDraw(); // initiate drawing mode

</script>

<style scoped lang="scss">

</style>