/*
 * Copyright 2017-19 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.
 */
import Debug from 'debug';
const debug = Debug('main/headless-pretty-print');
debug('loading');
import * as colors from 'colors/safe';
import ElementMimic from '../util/element-mimic';
import { isTable } from '../webapp/models/table';
import { isMixedResponse, isMessageBearingEntity } from '../models/entity';
import { isMultiModalResponse } from '../models/mmr/is';
import { isNavResponse } from '../models/NavResponse';
import { isHTML, isPromise } from '../util/types';
const log = console.log;
const error = console.error;
const verbose = process.argv.find(_ => _ === '-v');
const colorAlways = process.argv.find(_ => _ === '--color=always');
const neverColor = process.argv.find(_ => _ === '--no-color' || _ === '--no-colors');
const rawOutput = process.argv.find(_ => _ === '--raw-output'); // don't try to pretty-print the JSON; c.f. jq's --raw-output
/**
 * Determine whether how pretty we should make the output; if stdout
 * is a pipe, then we shouldn't pass through colors, unless
 * the user specified --color=always.
 *
 */
const stdoutIsFIFO = process.platform !== 'win32' && process.stdout.isTTY;
const noColor = neverColor || (stdoutIsFIFO && !colorAlways);
debug('stdoutIsFIFO', stdoutIsFIFO, noColor);
const colorMap = {
    'var(--color-brand-01)': colors.blue,
    'var(--color-brand-02)': colors.blue,
    'var(--color-support-02)': colors.blue,
    'var(--color-black)': colors.black,
    'var(--color-red)': colors.red,
    'var(--color-green)': colors.green,
    'var(--color-yellow)': colors.yellow,
    'var(--color-blue)': colors.blue,
    'var(--color-magenta)': colors.magenta,
    'var(--color-cyan)': colors.cyan,
    'var(--color-white)': colors.white,
    'var(--color-gray)': colors.gray,
    'var(--color-light-red)': colors.red,
    'var(--color-light-green)': colors.green,
    'var(--color-light-yellow)': colors.yellow
};
let graphicalShellIsOpen = false;
export const setGraphicalShellIsOpen = () => {
    graphicalShellIsOpen = true;
};
class DefaultPrettyOptions {
}
let firstPrettyDom = true; // so we can avoid initial newlines for headers
const prettyDom = (dom, logger = log, stream = process.stdout, _color, { columnWidths, extraColor } = new DefaultPrettyOptions()) => {
    debug('prettyDom');
    const isHeader = dom.nodeType === 'h1' || dom.nodeType === 'h2' || dom.nodeType === 'h3' || dom.nodeType === 'h4';
    const capitalize = false;
    const hasMargin = dom.className.indexOf('bx--breadcrumb-item--slash') >= 0 ||
        dom.className.indexOf('left-pad') >= 0 ||
        dom.style['margin'] ||
        dom.style['padding'];
    if (hasMargin) {
        stream.write(' ');
    }
    const extraColorChoice = isHeader || dom.hasStyle('fontWeight', 'bold')
        ? colors.bold
        : dom.hasStyle('fontWeight', 500)
            ? colors.green
            : dom.hasStyle('fontSize', '0.875em')
                ? colors.gray
                : extraColor || colors.reset;
    const colorFn = (dom.hasStyle('color') && colorMap[dom.style.color]) || _color;
    // debug('colors', isHeader, colorCode, color, extraColorChoice)
    if (isHeader) {
        // an extra newline before headers
        if (firstPrettyDom) {
            // don't emit a header margin for the very first thing
            // we print
            firstPrettyDom = false;
        }
        else {
            logger();
        }
    }
    if (dom.innerText) {
        const text = capitalize ? dom.innerText.charAt(0).toUpperCase() + dom.innerText.slice(1) : dom.innerText;
        stream.write(extraColorChoice(colorFn(text)));
    }
    const newline = () => {
        if (dom.nodeType === 'div' || isHeader) {
            // not perfect, but treat divs as line breakers
            logger();
        }
    };
    if (hasMargin) {
        stream.write(' ');
    }
    if (dom.innerText) {
        newline();
    }
    // recurse to the children of this fake DOM
    dom.children.forEach(child => prettyDom(child, logger, stream, _color, { extraColor: extraColorChoice }));
    // handle table rows and cells:
    if (dom.rows) {
        // scan the table for max column widths
        const columnWidths = [];
        dom.rows.forEach(row => {
            if (row.cells) {
                row.cells.forEach((cell, idx) => {
                    const length = cell.recursiveInnerTextLength();
                    if (!columnWidths[idx])
                        columnWidths[idx] = length;
                    else
                        columnWidths[idx] = Math.max(columnWidths[idx], length);
                });
            }
        });
        dom.rows.forEach(row => {
            prettyDom(row, logger, stream, _color, { columnWidths });
            logger(); // insert a newline after every row
        });
    }
    if (dom.cells) {
        dom.cells.forEach((cell, idx) => {
            prettyDom(cell, logger, stream, _color);
            if (columnWidths && idx < dom.cells.length - 1) {
                // pad out this column to the column width (except we don't
                // need padding in the last column)
                const slop = columnWidths[idx] - cell.recursiveInnerTextLength();
                for (let jj = 0; jj < slop; jj++) {
                    stream.write(' ');
                }
                // and then a few more to separate the columns
                stream.write('  ');
            }
        });
    }
    // trailing carriage return?
    if (isHeader && !dom.innerText) {
        logger();
    }
};
/**
 * Pretty print an object as JSON. This is mostly a stub for now, in
 * case we want to do something fancier.
 *
 */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const prettyJSON = (msg, logger = log) => {
    const serialized = JSON.stringify(msg, undefined, 2);
    logger(serialized);
};
/**
 * Render a name with an optional package name
 *
 */
const pn = (actionName, packageName) => colors.dim(`${packageName ? packageName + '/' : ''}`) + colors.blue(actionName);
/**
 * Render a date; if it is from today, show just the time
 *
 */
const prettyDate = (millis) => {
    const date = new Date(millis);
    const now = new Date();
    if (date.getUTCFullYear() === now.getUTCFullYear() &&
        date.getUTCMonth() === now.getUTCMonth() &&
        date.getUTCDate() === now.getUTCDate()) {
        return date.toLocaleTimeString();
    }
    else {
        return date.toLocaleString();
    }
};
/**
 * Turn an entity into a row, because this entity came as part of an
 * array of entities
 *
 */
const pp = (_) => colors.dim(_ ? 'public' : 'private'); // pretty publish
const pk = (_) => colors.green(_.find(({ key }) => key === 'exec').value); // pretty kind
const rowifyAction = ({ name, packageName, publish, annotations, version }) => ({
    name: pn(name, packageName),
    'published?': pp(publish),
    kind: pk(annotations),
    version: colors.dim(version)
});
const rowifyPackage = ({ name, publish, binding }) => ({
    name: pn(name),
    'published?': pp(publish),
    binding
});
const rowifySession = ({ sessionId, name, success, status, start }) => ({
    sessionId,
    app: pn(name),
    start: colors.dim(prettyDate(start)),
    status: status === 'pending' ? colors.yellow(status) : success ? colors.green(status) : colors.red(status)
});
const rowify = {
    compositions: ({ name, packageName, prettyKind }) => ({
        name: pn(name, packageName),
        type: prettyKind
    }),
    session: rowifySession,
    activations: ({ activationId, name }) => ({ activationId, name: pn(name) }),
    actions: rowifyAction,
    triggers: ({ name, publish }) => ({
        name: pn(name),
        'published?': pp(publish)
    }),
    packages: rowifyPackage,
    plugins: ({ name, attributes }) => {
        return {
            name: pn(name),
            version: colors.dim(attributes.find(({ key }) => key === 'version').value)
        };
    },
    _default: ({ key, name, attributes }) => {
        const row = {};
        row[key || 'name'] = pn(name);
        attributes.forEach(attr => {
            if (!attr.placeholderValue) {
                row[attr.key] = attr.value;
            }
        });
        return row;
    },
    sequence: rowifyAction,
    composer: rowifyAction,
    binding: rowifyPackage,
    live: rowifySession
};
/* function activationId(msg: Entity) {
  return (msg as { activationId: string }).activationId
}
function name(msg: Entity) {
  const entity = msg as { entity: { name: string } }
  return entity.entity.name
} */
/**
 * Pretty print routine that dispatches to the underlying smarter
 * pretty printers (such as prettyDom and prettyjson)
 *
 */
export const print = (msg, logger = log, stream = process.stdout, colorFn = colors.reset, ok = 'ok') => {
    if (ok === 'error') {
        colorFn = colors.red;
    }
    if (verbose && typeof msg === 'string') {
        debug('printing raw JSON, due to -v');
        prettyJSON(msg, logger);
        return;
    }
    if (msg && !graphicalShellIsOpen) {
        try {
            if (msg === true) {
                // true is the graphical shell's way of telling the repl to print 'ok'
                debug('printing plain true');
                logger(colors.green(ok));
            }
            else if (typeof msg === 'string' || typeof msg === 'boolean' || typeof msg === 'number') {
                stream.write(msg);
            }
            else if (ElementMimic.isFakeDom(msg)) {
                // msg is a DOM facade
                debug('printing fake dom');
                if (msg.className.indexOf('usage-error-wrapper') >= 0) {
                    // print usage errors to stdout
                    stream = process.stdout;
                    logger = log;
                    // color = 'reset'
                }
                prettyDom(msg, logger, stream, colorFn);
                logger();
            }
            else if (isPromise(msg)) {
                // msg is a promise; resolve it and try again
                debug('printing promise');
                return msg.then(msg => print(msg, logger, stream, colorFn, ok));
            }
            else if (isTable(msg)) {
                debug('printing table');
                // strip off header row, as we'll make our own
                const type = (msg.header && (msg.header.prettyType || msg.header.type)) ||
                    (msg.body.length > 0 && (msg.body[0].prettyType || msg.body[0].type));
                const print = (type && rowify[type]) || rowify._default;
                logger(require('columnify')(msg.body.map(print), {
                    headingTransform: (_) => colors.dim(_)
                }));
            }
            else if (isMixedResponse(msg)) {
                msg.forEach(_ => {
                    print(_);
                    stream.write('\n');
                });
                return logger(colors.green(ok));
            }
            else if (isMultiModalResponse(msg) || isNavResponse(msg)) {
                throw new Error('cannot format this response in headless mode');
            }
            else if (Array.isArray(msg)) {
                // msg is an array of stuff
                debug('printing array');
                if (msg.length > 0) {
                    try {
                        if (typeof msg[0] === 'string') {
                            // then we have a simple array of strings, not entities
                            ;
                            msg.forEach(_ => {
                                const logline = _.split(/(stdout|stderr)/);
                                if (logline && logline.length === 3 && !rawOutput) {
                                    // then this is a log line
                                    const colorFn = logline[1] === 'stdout' ? colors.reset : colors.red;
                                    logger(colors.dim(new Date(logline[0].trim()).toLocaleString()) + // timestamp
                                        // + ' ' + colors.yellow(logline[1])                    // stream (stdout, stderr)
                                        colorFn(logline[2].replace(/^:/, ''))); // log message
                                }
                                else {
                                    // otherwise, we're not sure what this is,
                                    // so do not attempt to render it in a special way
                                    logger(_);
                                }
                            });
                            return logger(colors.green(ok));
                        }
                    }
                    catch (err) {
                        error(err);
                    }
                }
            }
            else if (isHTML(msg)) {
                console.error('cannot format HTML message');
            }
            else if (isMessageBearingEntity(msg)) {
                if (ElementMimic.isFakeDom(msg.message)) {
                    // msg.message is a DOM facade
                    prettyDom(msg.message, logger, stream, colorFn);
                    logger();
                }
                else {
                    logger(msg.message);
                }
            }
            else if (typeof msg === 'object') {
                prettyJSON(msg, logger);
            }
            else {
                logger(colorFn(msg));
            }
        }
        catch (err) {
            debug('got an error', err);
            logger(colors.red(typeof msg === 'object' ? JSON.stringify(msg) : msg.toString()));
        }
    }
};
//# sourceMappingURL=headless-pretty-print.js.map