"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.getNamespaceBreadcrumbs = getNamespaceBreadcrumbs;
exports.initialCapital = initialCapital;
exports.isKubeTableResponse = isKubeTableResponse;
exports.stringToTable = exports.formatTable = exports.preprocessTable = void 0;

var _core = require("@kui-shell/core");

var _trafficLight = _interopRequireDefault(require("../model/traffic-light"));

var _resource = require("../model/resource");

var _2 = require("../../");

var _options = require("../../controller/kubectl/options");

var _cssForValue = _interopRequireDefault(require("./css-for-value"));

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

/*
 * Copyright 2018-2020 IBM Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
var __awaiter = void 0 && (void 0).__awaiter || function (thisArg, _arguments, P, generator) {
  function adopt(value) {
    return value instanceof P ? value : new P(function (resolve) {
      resolve(value);
    });
  }

  return new (P || (P = Promise))(function (resolve, reject) {
    function fulfilled(value) {
      try {
        step(generator.next(value));
      } catch (e) {
        reject(e);
      }
    }

    function rejected(value) {
      try {
        step(generator["throw"](value));
      } catch (e) {
        reject(e);
      }
    }

    function step(result) {
      result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);
    }

    step((generator = generator.apply(thisArg, _arguments || [])).next());
  });
};

const strings = (0, _core.i18n)('plugin-kubectl');
/** return an array with at least maxColumns entries */

const fillTo = (length, maxColumns) => {
  if (length >= maxColumns) {
    return [];
  } else {
    return new Array(maxColumns - length).fill('');
  }
};
/** decorate certain columns specially */


const outerCSSForKey = {
  NAME: 'entity-name-group',
  READY: 'a-few-numbers-wide kui--hide-in-narrower-windows',
  KIND: 'max-width-id-like entity-kind',
  NAMESPACE: 'entity-name-group hide-with-sidecar not-a-name',
  MESSAGE: 'not-too-compact',
  TYPE: 'hide-with-sidecar',
  CLUSTER: 'entity-name-group entity-name-group-narrow hide-with-sidecar',
  AUTHINFO: 'entity-name-group entity-name-group-narrow hide-with-sidecar',
  REFERENCE: 'entity-name-group entity-name-group-narrow hide-with-sidecar',
  'CREATED AT': 'hide-with-sidecar',
  // kubectl get deployment
  CURRENT: 'entity-name-group entity-name-group-extra-narrow text-center',
  DESIRED: 'entity-name-group entity-name-group-extra-narrow text-center',
  RESTARTS: 'very-narrow',
  'LAST SEEN': 'hide-with-sidecar entity-name-group-extra-narrow',
  'FIRST SEEN': 'hide-with-sidecar entity-name-group-extra-narrow',
  COUNT: 'keep-with-sidecar',
  // api-resources
  APIGROUP: 'hide-with-sidecar',
  REVISION: 'hide-with-sidecar',
  AGE: 'hide-with-sidecar very-narrow',
  'PORT(S)': 'entity-name-group entity-name-group-narrow hide-with-sidecar',
  SUBOBJECT: 'entity-name-group entity-name-group-extra-narrow' // helm ls

};
const cssForKey = {
  // kubectl get events
  NAME: 'entity-name',
  SOURCE: 'lighter-text smaller-text',
  SUBOBJECT: 'lighter-text smaller-text',
  MESSAGE: 'somewhat-smaller-text pre-wrap slightly-deemphasize',
  'CREATED AT': 'lighter-text smaller-text',
  AGE: 'slightly-deemphasize',
  'APP VERSION': 'pre-wrap slightly-deemphasize',
  UPDATED: 'slightly-deemphasize somewhat-smaller-text'
};
const tagForKey = {
  // READY: 'badge', // e.g. deployments
  REASON: 'badge',
  STATUS: 'badge'
};

const split = (str, splits, headerCells) => {
  return splits.map((splitIndex, idx) => {
    return {
      key: headerCells && headerCells[idx],
      value: str.substring(splitIndex, splits[idx + 1] || str.length).trim()
    };
  });
};
/**
 * Replace tab characters with a sequence of whitespaces
 *
 */


const detabbify = str => str.replace(/\t/g, '   ');
/**
 * Find the column splits
 *
 */


const preprocessTable = raw => {
  return raw.map(table => {
    const header = detabbify(table.substring(0, table.indexOf('\n')));
    const headerCells = header.split(/(\s\s)+\s?/).map(x => x && x.trim()).filter(x => x);
    const columnStarts = [];

    for (let idx = 0, jdx = 0; idx < headerCells.length; idx++) {
      const {
        offset,
        prefix
      } = idx === 0 ? {
        offset: 0,
        prefix: ''
      } : {
        offset: 1,
        prefix: ' '
      };
      const newJdx = header.indexOf(prefix + headerCells[idx] + ' ', jdx);

      if (newJdx < 0) {
        // last column
        jdx = header.indexOf(' ' + headerCells[idx], jdx);
      } else {
        jdx = newJdx;
      }

      columnStarts.push(jdx + offset);
    }

    return table.split(/\n/).filter(x => x).map(detabbify).map(line => split(line, columnStarts, headerCells));
  });
};
/**
 * Interpret READY column value "n/m" as a traffic light based on
 * whehter n/m === 1.
 *
 */


exports.preprocessTable = preprocessTable;

function cssForReadyCount(ready) {
  if (ready) {
    const [nReady, nTotal] = ready.split(/\//);
    const isDone = nReady && nTotal && nReady === nTotal;
    return isDone ? _trafficLight.default.Green : _trafficLight.default.Yellow;
  }
}
/** @return a namespace breadcrumb, either from the one given by args, or using the default from context */


function getNamespaceBreadcrumbs(entityType, args) {
  return __awaiter(this, void 0, void 0, function* () {
    const ns = yield (0, _resource.isClusterScoped)(entityType) ? undefined : (0, _options.getNamespaceAsExpressed)(args) || (0, _options.isForAllNamespaces)(args.parsedOptions) && strings('all') || args.execOptions.type === _core.ExecType.TopLevel && !(0, _core.inBrowser)() && (yield (0, _2.getCurrentDefaultNamespace)(args));
    return ns ? [{
      label: ns
    }] : undefined;
  });
}
/** HELLO -> Hello, which is not possible to do with CSS */


function initialCapital(name) {
  return name[0] + name.slice(1).toLowerCase();
}

const formatTable = (command, verb, entityTypeFromCommandLine, args, preTable, nameColumn = 'NAME') => __awaiter(void 0, void 0, void 0, function* () {
  const {
    parsedOptions: options
  } = args; // for helm status, table clicks should dispatch to kubectl;
  // otherwise, stay with the command (kubectl or helm) that we
  // started with

  const isHelmStatus = command === 'helm' && verb === 'status';
  const drilldownCommand = isHelmStatus ? 'kubectl' : command;
  const drilldownVerb = (verb === 'get' || verb === 'top' ? 'get' : command === 'helm' && (verb === 'list' || verb === 'ls') ? 'get' : isHelmStatus ? 'get' : verb === 'krew' ? verb : undefined) || undefined; // helm doesn't support --output

  const drilldownFormat = (drilldownCommand === 'kubectl' || drilldownCommand === 'oc') && drilldownVerb === 'get' ? '-o yaml' : '';
  const ns = options.n || options.namespace;
  const drilldownNamespace = ns ? `-n ${(0, _core.encodeComponent)(ns)}` : '';
  const kindColumnIdx = preTable[0].findIndex(({
    key
  }) => key === 'KIND');

  const drilldownKind = (nameSplit, row) => {
    if (drilldownVerb === 'get') {
      const kind = kindColumnIdx >= 0 ? row[kindColumnIdx].value : nameSplit.length > 1 ? nameSplit[0] : entityTypeFromCommandLine;
      return kind ? ' ' + kind : '';
      /* } else if (drilldownVerb === 'config') {
        return ' use-context'; */
    } else if (drilldownVerb === 'krew') {
      return ' ' + entityTypeFromCommandLine;
    } else {
      return '';
    }
  }; // maximum column count across all rows


  const nameColumnIdx = preTable[0].findIndex(({
    key
  }) => key === nameColumn);
  const namespaceColumnIdx = preTable[0].findIndex(({
    key
  }) => key === 'NAMESPACE');
  const maxColumns = preTable.reduce((max, columns) => Math.max(max, columns.length), 0); // for kubectl get all... the actual entity type of each table is
  // manifested in the name cell, e.g. "_pod_/mypod"

  let entityTypeFromRows;
  const rows = preTable.map((rows, idx) => {
    const name = nameColumnIdx >= 0 ? rows[nameColumnIdx].value : '';
    const nameSplit = name.split(/\//); // for "get all", the name field will be <kind/entityName>

    const nameForDisplay = nameSplit[1] || rows[0].value;
    const nameForDrilldown = nameSplit[1] || name;
    const css = '';
    const firstColumnCSS = idx === 0 || rows[0].key !== 'CURRENT' ? css : 'selected-entity'; // if we have a "name split", e.g. "pod/myPod", then keep track of the "pod" part

    if (nameSplit[1]) {
      if (!entityTypeFromRows) {
        entityTypeFromRows = nameSplit[0];
      } else if (entityTypeFromRows !== nameSplit[0]) {
        entityTypeFromRows = undefined;
      }
    }

    const rowIsSelected = rows[0].key === 'CURRENT' && rows[0].value === '*';
    const rowCSS = [rowIsSelected ? 'selected-row' : ''].filter(_ => _); // if there isn't a global namespace specifier, maybe there is a row namespace specifier
    // we use the row specifier in preference to a global specifier -- is that right?

    const ns = namespaceColumnIdx >= 0 && command !== 'helm' && `-n ${(0, _core.encodeComponent)(rows[namespaceColumnIdx].value)}` || drilldownNamespace || ''; // idx === 0: don't click on header row

    const onclick = idx === 0 ? false : drilldownVerb ? `${drilldownCommand} ${drilldownVerb}${drilldownKind(nameSplit, rows)} ${(0, _core.encodeComponent)(nameForDrilldown)} ${drilldownFormat} ${ns}` : false;
    const header = idx === 0 ? 'header-cell' : ''; // for `k get events`, show REASON and MESSAGE columns when sidecar open

    const columnVisibleWithSidecar = new RegExp(/STATUS|REASON|MESSAGE/i); // show TrafficLight.Red if it's a failure reason in `k  get events`

    const maybeRed = reason => {
      return /failed/i.test(reason) ? _trafficLight.default.Red : '';
    };
    /**
     * rowKey is the unique string that distinguishes each row
     * option 1. name
     * option 2. `first column`-`name`, e.g. --all-namespaces
     * option 3. `first column`-`idx` when there's no name column, e.g. k get events
     *
     */


    const rowKey = name ? (0, _options.isForAllNamespaces)(options) ? `${rows[0].value}-${name}` : name : `${rows[0].value}-${idx}`;
    return {
      key: rows[0].key,
      name: nameForDisplay,
      rowKey,
      fontawesome: idx !== 0 && rows[0].key === 'CURRENT' && 'fas fa-check',
      onclick: nameColumnIdx === 0 && onclick,
      onclickSilence: true,
      css: firstColumnCSS + (rows[0].key === nameColumn ? ' kui--table-cell-is-name' : ''),
      rowCSS,
      outerCSS: `${header} ${outerCSSForKey[rows[0].key] || ''}`,
      attributes: rows.slice(1).map(({
        key,
        value: column
      }, colIdx) => ({
        key,
        tag: idx > 0 && tagForKey[key],
        onclick: colIdx + 1 === nameColumnIdx && onclick,
        outerCSS: header + ' ' + (outerCSSForKey[key] || '') + (colIdx <= 1 || colIdx === nameColumnIdx - 1 || columnVisibleWithSidecar.test(key) ? '' : ' hide-with-sidecar'),
        css: css + ' ' + (idx > 0 && cssForKey[key] || '') + ' ' + (_cssForValue.default[column] || key === 'READY' && cssForReadyCount(column) || maybeRed(column)),
        value: key === 'STATUS' && idx > 0 ? column || strings('Unknown') : column
      })).concat(fillTo(rows.length, maxColumns))
    };
  });
  const breadcrumbs = yield getNamespaceBreadcrumbs(entityTypeFromCommandLine, args);
  return {
    header: Object.assign(rows[0], {
      key: rows[0].key || rows[0].name,
      name: initialCapital(rows[0].name),
      attributes: (rows[0].attributes || []).map(_ => Object.assign(_, {
        value: initialCapital(_.value)
      }))
    }),
    body: rows.slice(1),
    noSort: true,
    title: entityTypeFromRows || entityTypeFromCommandLine || '',
    breadcrumbs
  };
});

exports.formatTable = formatTable;

function isKubeTableResponse(response) {
  return typeof response === 'string' || (0, _core.isTable)(response) || Array.isArray(response) && response.length > 0 && (0, _core.isTable)(response[0]);
}

function withNotFound(table, stderr) {
  const notFounds = stderr.split(/\n/).filter(_ => /NotFound/.test(_)).map(_ => _.match(/"([^"]+)" not found/)[1]);

  if (notFounds.length > 0) {
    const statusIdx = table.body.length === 0 ? -1 : table.body[0].attributes.findIndex(_ => /STATUS/i.test(_.key));
    const attributes = table.body.length === 0 ? [] : Array(table.body[0].attributes.length).fill(undefined).map((_, idx) => {
      const cell = {};

      if (idx === statusIdx) {
        const value = 'Offline';
        cell.value = value;
        cell.tag = tagForKey['STATUS'];
        cell.outerCSS = outerCSSForKey['STATUS'];
        cell.css = [cssForKey['STATUS'], _cssForValue.default[value]].join(' ');
      }

      return cell;
    });
    notFounds.forEach(name => {
      table.body.push({
        name,
        isDeleted: true,
        attributes
      });
    });
  }

  return table;
}
/**
 * Display the given string as a REPL table
 *
 */


const stringToTable = (decodedResult, stderr, args, command, verb, entityType, nameColumn) => __awaiter(void 0, void 0, void 0, function* () {
  // the ?=\s+ part is a positive lookahead; we want to
  // match only "NAME " but don't want to capture the
  // whitespace
  const preTables = preprocessTable(decodedResult.split(/^(?=LAST SEEN|NAMESPACE|NAME\s+)/m));

  if (preTables && preTables.length === 1 && preTables[0].length === 0) {
    // degenerate case of "really not a table"
    return decodedResult || stderr;
  } else if (preTables && preTables.length >= 1) {
    // try use display this as a table
    if (preTables.length === 1) {
      const T = yield formatTable(command, verb, entityType, args, preTables[0], nameColumn);

      if (args.execOptions.filter) {
        T.body = args.execOptions.filter(T.body);
      }

      return withNotFound(T, stderr);
    } else {
      return Promise.all(preTables.map(preTable => __awaiter(void 0, void 0, void 0, function* () {
        const T = yield formatTable(command, verb, entityType, args, preTable);

        if (args.execOptions.filter) {
          T.body = args.execOptions.filter(T.body);
        }

        return withNotFound(T, stderr);
      })));
    }
  } else {
    // otherwise, display the raw output
    return decodedResult;
  }
});

exports.stringToTable = stringToTable;