<template>
  <builder-panel :order="order" :builder="builder" :build-tranches="buildTranches"
                 :adjust-shapes="adjustShapes" :delete-shapes="deleteShapes" :name="name">
    <div style="min-width: 4em; font-size: larger" :style="colorStyle"
         class="d-flex flex-column">
<!--
      <div class="flex-row align-items-center">
        <v-btn variant="outlined" style="width: 8em"
               @click="()=>{if (props.builder.breakout!==undefined) props.builder.breakout=!props.builder.breakout}">{{ name }}</v-btn>
        <div class="description w-100 text-center">{{description}}</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"
                    min="1" :max="MAX_RUNGS"
                    :disabled="rungsDisabled"
                    style="width: 6.6em; max-height: 2.5em; height: 2.5em"
                    id="rungs"
      />
      <one-time-hint name="rungs" activator="#rungs" after="choose-builder"
                     text="↓ Try increasing rungs!" location="top"
                     :when="rungs===1&&endpoints[0]!==null"
                     :on-complete="()=>track('rungs')"
      />
      <v-tooltip v-if="builder.breakout!==undefined"
                 :text="order.buy?'Breakout orders buy above the breakout line':'Breakdown orders sell below the breakdown line'">
        <template #activator="{ props }">
          <div v-bind="props">
            <v-switch v-model="breakout" :label="order.buy?'Breakout':'Breakdown'"
                      persistent-hint :color="switchColor" :base-color="switchColor" hide-details direction="vertical"
                      density="compact"/>
            <div class="mx-auto">
              <span style="font-size: .7em; vertical-align: top"
                    :style="builder.breakout?{color:new Color(color).lighten(0.5).string()}:null">
                {{ description }}
              </span>
            </div>
          </div>
        </template>
      </v-tooltip>
    </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 justify-start">
      <div class="d-flex align-center mt-2">
        <div id="balance-slider">
          <v-slider v-if="rungs>1" :direction="orientation?'vertical':'horizontal'" min="-100" max="100" v-model="balance100"
                    class="no-slider-bg ml-2 mr-4" hide-details/>
        </div>
        <one-time-hint name="balance-slider" activator="#balance-slider" after="rungs"
               text="↓ Slide the amount balance ↓" location="top"
               :when="balance100===0"
               :on-complete="()=>track('balance-slider')"
        />
        <v-text-field type="number" v-model="balance100" min="-100" max="100"
                      density="compact" hide-details variant="outlined" label="Balance" step="5"
                      class="balance">
          <template v-slot:prepend>
            <v-btn icon="mdi-scale-balance" variant="plain" @click="builder.balance=0"/>
          </template>
        </v-text-field>
      </div>
    </div>
  </builder-panel>
</template>

<script setup>
import BuilderPanel from "@/components/chart/BuilderPanel.vue";
import {deleteBuilder, linearWeights, useChartOrderStore} from "@/orderbuild.js";
import {linspace, sideColor} from "@/misc.js";
import {computed, watchEffect} from "vue";
import Color from "color";
import {cancelDrawing} from "@/charts/chart.js";
import {
  devectorize,
  vectorAdd,
  vectorDiv,
  vectorEquals,
  vectorIsNull,
  vectorIsZero,
  vectorize,
  vectorMul,
  vectorNeg,
  vectorSub
} from "@/vector.js";
import {logicalXOR} from "@/common.js";
import OneTimeHint from "@/components/OneTimeHint.vue";
import {track} from "@/track.js";

const co = useChartOrderStore()
const endpoints = defineModel('modelValue')  // 2-item list of points/values
const props = defineProps({
  name: String,
  description: String,
  order: Object,
  builder: Object,
  buildTranches: Function,
  stdWidth: [Number, Array],
  shape: Function,  // shape() -> Shape
  mode: { type: Number, default: 0 }, // rung addition mode: 0 = split, 1 = extend
  flip: { type: Boolean, default: false }, // if true, the balance slider is flipped upside-down
  orientation: { type: Number, default: 1 },  // 0 = horizontal slider, 1 = vertical
  // values may be scalars or vector arrays
  getModelValue: Function,  // getModelValue(model) -> value
  setModelValue: Function,  // setModelValue(model,value) -> void
  setValues: Function,  // setValues(values:Array) -> void
  setWeights: Function,  // setWeights(values:Array) -> void
  setShapes: { type: Function, default: null },  // setShapes([shapeA, innerShape, ..., shapeB]) -> void
})

const flippedSign = computed(()=>props.flip?-1:1)

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

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

let lastBuy = null
watchEffect(()=>{
  if (props.order.buy!==lastBuy) {
    lastBuy = props.order.buy
    props.builder.color=computeDefaultColor()
  }
})

const breakout = computed({
  get() {return !logicalXOR(props.builder.breakout, props.order.buy)},
  set(v) {props.builder.breakout = !logicalXOR(v, props.order.buy)},
})

function setEndpoints(a, b) {
  endpoints.value = [devectorize(a), devectorize(b)]
}

const MAX_RUNGS = 10 // todo remove arbitrary limit

const rungs = computed({
  get() {
    return props.builder.rungs
  },
  set(r) {
    if (!r) {
      props.builder.rungs = 1
      return
    }
    if (r>MAX_RUNGS)
      r = MAX_RUNGS
    let [a,b] = endpoints.value
    a = vectorize(a)
    b = vectorize(b)
    r = Number(r)
    const prevR = Number(props.builder.rungs)
    props.builder.rungs = r
    if ( prevR === 1 && r > 1 ) {
      // convert single shape to a range
      if (props.mode===0) {
        const width = vectorize(props.stdWidth)
        const mid = vectorize(a)
        // console.log('stdWidth', width)
        const halfWidth = vectorDiv(width,2);
        a = vectorAdd(mid, vectorNeg(halfWidth) )
        b = vectorAdd(mid, halfWidth)
        setEndpoints(a,b)
      }
      else if (props.mode===1 ) {
        setEndpoints(a, vectorAdd(a,props.stdWidth))
      }
      else
        throw Error(`Unknown rung mode ${props.mode}`)
    }
    else if ( prevR > 1 && r === 1 ) {
      // convert from a range to a single shape
      if (props.mode===0)
        a = vectorDiv(vectorAdd(a,b), 2)
      b = null
      setEndpoints(a,b)
    }
    else {
      // from multi to multi
      if (props.mode===1) {
        const width = vectorDiv(vectorSub(b, a), (prevR-1))
        b = vectorAdd(a, vectorMul(width, (r-1)))
        setEndpoints(a,b)
      }
    }
  }
})

const rungsDisabled = computed(()=>endpoints.value[0]===null)

const values = computed(()=>{
  let [a, b] = endpoints.value
  // console.log('generating values for endpoints', a, b)
  a = vectorize(a)
  b = vectorize(b)
  const r = props.builder.rungs
  let result
  if ( !r || vectorIsNull(a) )
    result = []  // no data
  else if (r===1)
    result = [devectorize(a)]  // single shape
  else {
    const columns = []
    for (let i=0; i<a.length; i++)
      columns.push(linspace(a[i], b[i], r))  // linear spacing for each dimension
    // transpose
    result = []
    for (let i=0; i<r; i++) {
      const vector = []
      for (let j=0; j<a.length; j++)
        vector.push(columns[j][i])
      result.push(devectorize(vector))
    }
  }
  props.setValues(result)
  // console.log('values', result)
  return result;
})


const weights = computed(() => {
  // const balance = props.flip ? -props.builder.balance : props.builder.balance
  const most = 0.998
  let balance = -props.builder.balance
  if (balance <= -1)
    balance = -most
  else if (balance >= 1)
    balance = most
  const ws = linearWeights(props.builder.rungs, balance)
  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) {
    console.error('set color', 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 switchColor = computed(()=>props.builder.breakout ? color.value : null)
const colorStyle = computed(() => {
  // return {'color': color.value}
  return {}
})


//
// 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) {
  setModelValue(model, value)
  return new props.shape(model, onModel, onDelete)  // props.shape is the constructor function
}


function getModelValue(model) {
  return vectorize(props.getModelValue(model));
}


function setModelValue(model, value) {
  props.setModelValue(model, devectorize(value))
}


function translateOnModel(shape) {
  const oldOnModel = shape.onModel
  shape.onModel = function (model, oldModel) {
    oldOnModel.call(this, ...arguments)
    if (!this.beingDragged())
      return
    const prev = getModelValue(oldModel)
    const cur = vectorize(getModelValue(model))
    const delta = vectorSub(cur, prev)
    // console.log('translateOnModel delta', shape.id, prev, cur, delta)
    let [a, b] = endpoints.value
    a = vectorize(a)
    if (!vectorIsZero(delta)) {
      a = vectorAdd(a, delta)
      if (rungs.value > 1)
        b = vectorAdd(vectorize(b), delta)
      setEndpoints(a,b)
    }
  }
}

function setModelColor(model) {
  if (model.color !== undefined && model.color !== color.value)
    color.value = model.color
}

const shapeA = createShape(endpoints.value[0], {color: defaultColor},
    function (model) {
      const value = getModelValue(model);
      if ( !vectorEquals(value, endpoints.value[0]) )
        setEndpoints(value, endpoints.value[1])
      setModelColor(model)
    },
    deleteSelf)

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

const shapeB = createShape(endpoints.value[1], {color:defaultColor},
    function (model) {
      const value = getModelValue(model);
      if ( !vectorEquals(value, endpoints.value[1]) )
        setEndpoints(endpoints.value[0], value)
      setModelColor(model)
    },
    deleteSelf)

function interiorOnModel(model) {
  setModelColor(model)
}

let interiorShapes = []

function createInteriorShape(index) {
  const shape = new props.shape(makeModel(index), interiorOnModel, deleteSelf)
  translateOnModel(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 buy = props.order.buy
  const above = buy === props.builder.breakout
  const result = {
    color: color.value,
    allocation: alloc,
    maxAllocation: Math.max(...weights.value),
    amount: props.order.amount * alloc,
    baseSymbol: co.selectedSymbol.base.s,
    amountSymbol: amountSymbol.value,
    textLocation: above ? 'above' : 'below',
    breakout: props.builder.breakout,
    extraText: null,
    buy,
  }
  setModelValue(result, values.value[index])
  return result
}


function adjustShapes() {
  // this is where all the shapes are created or adjusted
  // console.error('adjustShapes()', ...endpoints.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 = []
    if (props.setShapes)
      props.setShapes([])
  }
  else if (vs.length === 1) {
    //
    // SINGLE SHAPE
    //
    shapeA.setModel(makeModel(0))
    shapeB.delete()
    if (interiorShapes.length) {
      for( const shape of interiorShapes )
        shape.delete()
      interiorShapes = []
    }
    if (props.setShapes)
      props.setShapes([shapeA])
  }
  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))
    }
    if (props.setShapes)
      props.setShapes([shapeA, ...interiorShapes, shapeB])
  }
}


function deleteSelf() {
  if (endpoints.value[0]===null)
    cancelDrawing()
  deleteBuilder(props.order, props.builder);
}

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


if (!endpoints.value[0])
  shapeA.createOrDraw(); // initiate drawing mode
else
  adjustShapes()

</script>

<style scoped lang="scss">
:deep(.v-slider.v-input--vertical > .v-input__control) {
  min-height: 5em !important;
}
:deep(.v-slider.v-input--horizontal > .v-input__control) {
  min-width: 5em !important;
}
:deep(.v-slider.no-slider-bg .v-slider-track__fill) {
  background-color: inherit !important;
}
.balance {
  min-width: 9em;
  max-width: 12em;
}
.description {
  font-size: 0.6em;
}
</style>
