text: import day from 'dayjs';
  import { insertAt, filterBy } from '@/utils/array';
  import {
    ADD_SIDECAR, _FLAGGED, MODE, _CREATE, _CLONE, _STAGE
  } from '@/config/query-params';
  import { escapeHtml } from '@/utils/string';
  import { DATE_FORMAT, TIME_FORMAT } from '@/store/prefs';
  import { PRIVATE } from '@/plugins/steve/resource-proxy';
  import { RIO } from '@/config/types';
  import { formatSi } from '@/utils/units';
  import { get } from '@/utils/object';
  
  const EMPTY = {};
  
  export default {
    applyDefaults(mode) {
      const spec = this.spec;
  
      if ( mode === _CREATE || mode === _CLONE ) {
        delete spec.app;
        spec.version = 'v0';
      } else if ( mode === _STAGE ) {
        spec.app = this.app;
        delete spec.version;
      }
  
      if ( mode === _CREATE ) {
        spec.weight = 10000;
      } else if ( mode === _CLONE ) {
        delete spec.weight;
      } else if ( mode === _STAGE ) {
        spec.weight = 0;
      }
    },
  
    app() {
      const spec = this.spec || EMPTY;
      const status = this.status || EMPTY;
      const metadata = this.metadata || EMPTY;
  
      return spec.app || status.computedApp || metadata.name;
    },
  
    version() {
      const spec = this.spec || EMPTY;
      const status = this.status || EMPTY;
      const uid = ((this.metadata || EMPTY)['uid'] || '').replace(/-.*$/, '');
  
      return spec.version || status.computedVersion || uid || '?';
    },
  
    nameDisplay() {
      const version = this.version;
  
      if ( version === 'v0' ) {
        return this.app;
      }
  
      return `${ this.app }@${ this.version }`;
    },
  
    namespaceApp() {
      return `${ this.metadata.namespace }:${ this.app }`;
    },
  
    imageDisplay() {
      if ( this.spec.build && !this.spec.image ) {
        return 'Building from Git...';
      }
  
      return (this.spec.image || '')
        .replace(/^(index\.)?docker.io\/(library\/)?/i, '')
        .replace(/@sha256:[0-9a-f]+$/i, '')
        .replace(/:latest$/i, '')
        .replace(/localhost:5442\/(.*)/i, '$1 (local)');
    },
  
    createdDisplay() {
      const dateFormat = escapeHtml( this.$rootGetters['prefs/get'](DATE_FORMAT));
      const timeFormat = escapeHtml( this.$rootGetters['prefs/get'](TIME_FORMAT));
  
      return day(this.metadata.creationTimestamp).format(`${ dateFormat } ${ timeFormat }`);
    },
  
    versionWithDateDisplay() {
      return `${ this.version } (${ this.createdDisplay })`;
    },
  
    scales() {
      const status = this.status || {};
      const scaleStatus = status.scaleStatus || {};
      const auto = !!this.spec.autoscale;
      const fixed = (typeof this.spec.replicas === 'undefined' ? 1 : this.spec.replicas || 0);
      const available = scaleStatus.available || 0;
      const current = (typeof status.computedReplicas === 'undefined' ? available : status.computedReplicas || 0);
      const unavailable = scaleStatus.unavailable || 0;
      const global = this.spec.global === true;
  
      let desired = fixed;
      let min, max;
  
      if ( auto ) {
        min = this.spec.autoscale.minReplicas;
        max = this.spec.autoscale.maxReplicas;
        desired = `${ min } - ${ max }`;
      }
  
      if ( global ) {
        desired = current;
      } else if ( typeof this[PRIVATE].pendingScale === 'number' ) {
        desired = this[PRIVATE].pendingScale;
      }
  
      const missing = Math.max(0, desired - available - unavailable);
  
      return {
        global,
        auto,
        min,
        max,
        current,
        desired,
        available,
        unavailable,
        starting:    missing > 0 ? missing : 0,
        stopping:    missing < 0 ? -1 * missing : 0,
      };
    },
  
    showDesiredScale() {
      const scales = this.scales;
  
      return !scales.global && scales.current !== scales.desired;
    },
  
    complexScale() {
      const { stopping, starting, unavailable } = this.scales;
  
      return stopping !== 0 || starting !== 0 || unavailable !== 0;
    },
  
    scaleParts() {
      const {
        available, unavailable, starting, stopping
      } = this.scales;
      const out = [
        {
          label:     'Available',
          color:     'bg-success',
          textColor: 'text-success',
          value:      available
        },
  
        {
          label:     'Unavailable',
          color:     'bg-error',
          textColor: 'text-error',
          value:     unavailable
        },
      ];
  
      if ( starting ) {
        out.push({
          label:     'Starting',
          color:     'bg-info',
          textColor: 'text-info',
          value:     starting
        });
      }
  
      if ( stopping ) {
        out.push({
          label:     'Stopping',
          color:     'bg-warning',
          textColor: 'text-warning',
          value:     stopping
        });
      }
  
      return out;
    },
  
    scaleUp() {
      return () => {
        let scale;
  
        if ( this.scales.global ) {
          return;
        }
  
        if ( this[PRIVATE].scaleTimer ) {
          scale = this[PRIVATE].pendingScale;
        } else {
          scale = this.scales.desired;
        }
  
        scale = scale || 0;
  
        this[PRIVATE].pendingScale = scale + 1;
        this.saveScale();
      };
    },
  
    scaleDown() {
      return () => {
        let scale;
  
        if ( this.scales.global ) {
          return;
        }
  
        if ( this[PRIVATE].scaleTimer ) {
          scale = this[PRIVATE].pendingScale;
        } else {
          scale = this.scales.desired;
        }
  
        scale = scale || 1;
  
        this[PRIVATE].pendingScale = Math.max(scale - 1, 0);
        this.saveScale();
      };
    },
  
    saveScale() {
      return () => {
        if ( this[PRIVATE].scaleTimer ) {
          clearTimeout(this[PRIVATE].scaleTimer);
        }
  
        this[PRIVATE].scaleTimer = setTimeout(async() => {
          try {
            await this.patch([{
              op:    'replace',
              path:  '/spec/replicas',
              value: this[PRIVATE].pendingScale
            }]);
          } catch (err) {
            this.$dispatch('growl/fromError', { title: 'Error updating scale', err }, { root: true });
          }
  
          this[PRIVATE].scaleTimer = null;
          this[PRIVATE].pendingScale = null;
        }, 500);
      };
    },
  
    allVersions() {
      const services = this.$getters['all'](RIO.SERVICE);
  
      const out = filterBy(services, {
        app:                  this.app,
        'metadata.namespace': this.metadata.namespace,
      });
  
      return out;
    },
  
    weightsOfApp() {
      let desired = 0;
      let current = 0;
      let count = 0;
  
      for ( const service of this.allVersions ) {
        const weights = service.weights;
  
        desired += weights.desired || 0;
        current += weights.current || 0;
        count++;
      }
  
      return {
        desired,
        current,
        count
      };
    },
  
    weights() {
      let current = 0;
      let desired = 0;
  
      const fromSpec = this.spec.weight;
  
      if ( this.status ) {
        const fromStatus = this.status.computedWeight;
  
        if ( typeof fromStatus === 'number' ) {
          current = fromStatus;
        } else if ( typeof fromSpec === 'number' ) {
          current = fromSpec;
        }
  
        if ( typeof fromSpec === 'number' ) {
          desired = fromSpec;
        } else if ( typeof fromStatus === 'number' ) {
          desired = fromStatus;
        }
      }
  
      return { current, desired };
    },
  
    weightsPercent() {
      const self = this.weights;
      const app = this.weightsOfApp;
  
      let desired = 0;
      let current = 0;
  
      if ( self.desired && app.desired ) {
        desired = self.desired / app.desired * 100;
      }
  
      if ( self.current && app.current ) {
        current = self.current / app.current * 100;
      }
  
      return { current, desired };
    },
  
    saveWeightPercent() {
      return (newPercent) => {
        const appInfo = this.weightsOfApp;
        const totalWeight = appInfo.desired;
        const currentPercent = (totalWeight === 0 ? 0 : this.weights.desired / totalWeight);
        const currentWeight = this.spec.weight || 0;
        const totalOfOthers = totalWeight - currentWeight;
        const count = appInfo.count;
  
        if ( currentPercent === 100 ) {
          if ( newPercent === 100 ) {
            return;
          } else if ( newPercent === 0 ) {
            return this.saveWeight(0);
          }
  
          const weight = newWeight(100 - newPercent) / (count - 1);
  
          for ( const svc of this.allVersions ) {
            if ( svc.id === this.id ) {
              continue;
            }
  
            svc.saveWeight(weight);
          }
        } else if ( totalOfOthers === 0 || newPercent === 100 ) {
          this.saveWeight(10000);
  
          for ( const svc of this.allVersions ) {
            if ( svc.id === this.id ) {
              continue;
            }
  
            svc.saveWeight(0);
          }
        } else {
          const weight = newWeight(newPercent);
  
          this.saveWeight(weight);
        }
  
        function newWeight(percent) {
          if ( percent === 0 ) {
            return 0;
          }
  
          const out = Math.round(totalOfOthers / (1 - (percent / 100))) - totalOfOthers;
  
          return out;
        }
      };
    },
  
    saveWeight() {
      return async(neu) => {
        console.log('Save Weight', this.spec.app, this.spec.version, neu); // eslint-disable-line no-console
        try {
          await this.patch([{
            op:    'replace',
            path:  '/spec/weight',
            value: neu
          }]);
        } catch (err) {
          this.$dispatch('growl/fromError', { title: 'Error updating weight', err }, { root: true });
        }
      };
    },
  
    pauseOrResume() {
      return async(pause = true) => {
        try {
          await this.patch([{
            op:    'replace',
            path:  '/spec/rollout/pause',
            value: pause
          }]);
        } catch (err) {
          this.$dispatch('growl/fromError', { title: 'Error updating pause', err }, { root: true });
        }
      };
    },
  
    pause() {
      this.pauseOrResume(true);
    },
  
    resume() {
      this.pauseOrResume(false);
    },
  
    goToStage() {
      return (moreQuery = {}) => {
        const location = this.detailLocation;
  
        location.query = {
          ...location.query,
          [MODE]: _STAGE,
          ...moreQuery
        };
  
        this.currentRouter().push(location);
      };
    },
  
    _availableActions() {
      const links = this.links || {};
      const out = this._standardActions;
  
      let isPaused = false;
  
      if ( this.spec.rollout && this.spec.rollout.pause ) {
        isPaused = true;
      }
  
      insertAt(out, 2, {
        action:  'pause',
        label:   'Pause Rollout',
        icon:    'icon icon-gear',
        enabled:  !!links.update && !isPaused,
      });
  
      insertAt(out, 2, {
        action:  'resume',
        label:   'Resume Rollout',
        icon:    'icon icon-gear',
        enabled:  !!links.update && isPaused,
      });
  
      insertAt(out, 2, {
        action:  'addSidecar',
        label:   'Add a Sidecar',
        icon:    'icon icon-circle-plus',
        enabled:  !!links.update,
      });
  
      insertAt(out, 2, {
        action:  'goToStage',
        label:   'Stage New Version',
        icon:    'icon icon-copy',
        enabled:  !!links.update,
      });
  
      insertAt(out, 2, { divider: true });
  
      return out;
    },
  
    addSidecar() {
      return () => {
        return this.goToEdit({ [ADD_SIDECAR]: _FLAGGED });
      };
    },
  
    networkBytes() {
      const read = get(this, 'metadata.computed.fields.readBytesPerSecond') || 0;
      const write = get(this, 'metadata.computed.fields.writeBytesPerSecond') || 0;
  
      return read + write;
    },
  
    networkDisplay() {
      return formatSi(this.networkBytes, { suffix: 'Bps' });
    },
  
    p95() {
      const out = get(this, 'metadata.computed.fields.p95') || 0;
  
      return out;
    },
  
    p95Display() {
      return `${ this.p95 }ms`;
    },
  
    connections() {
      const out = get(this, 'metadata.computed.fields.openConnections') || 0;
  
      return out;
    },
  };
  