import { __decorate } from "tslib";
import { html, nothing } from 'lit';
import { property, state } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { Events as CoreEvents, HttpProjectKind, ProjectExecutionKind, } from '@api-client/core/build/browser.js';
import ApiElement from '../ApiElement.js';
import { Events } from '../../events/Events.js';
import '@github/relative-time-element';
import '../../define/http-project/project-run-report.js';
import '../../define/ui/ui-checkbox.js';
import '../../define/ui/ui-text-field.js';
import '../../define/ui/ui-button.js';
import '../../define/ui/ui-progress.js';
import '../../define/ui/ui-icon.js';
import '../../define/ui/ui-dropdown-list.js';
import '../../define/ui/ui-list.js';
import '../../define/ui/ui-list-item.js';
import { StoreBroadcast } from '../../http-client/store/StoreBroadcast.js';
/**
 * @fires change - When the configuration of the runner change.
 */
export default class ProjectRunner extends ApiElement {
    constructor() {
        super();
        this.history = [];
        this.debounceTimeout = 100;
        this.channel = new BroadcastChannel(StoreBroadcast.projectRuns);
        this.handleBroadcast = this.handleBroadcast.bind(this);
    }
    connectedCallback() {
        super.connectedCallback();
        this.channel.addEventListener('message', this.handleBroadcast);
    }
    disconnectedCallback() {
        super.disconnectedCallback();
        this.channel.removeEventListener('message', this.handleBroadcast);
    }
    willUpdate(cp) {
        super.willUpdate(cp);
        if (cp.has('project') || cp.has('folder')) {
            this.setupRoot();
        }
    }
    handleBroadcast(e) {
        const event = e.data;
        switch (event.operation) {
            case 'created':
                this.handleProjectRunCreated(event);
                break;
            case 'deleted':
                this.handleProjectRunDeleted(event);
                break;
            default:
        }
    }
    setupRoot() {
        const { project, folder } = this;
        if (!project) {
            this.root = undefined;
            return;
        }
        if (!folder) {
            this.root = project;
        }
        else {
            this.root = project.findFolder(folder);
        }
        this.resetHistory();
        this.queryPageDebounce();
    }
    handleProjectRunCreated(event) {
        const { project, folder } = this;
        if (!project) {
            return;
        }
        if (project.key !== event.project) {
            return;
        }
        const item = event.data;
        if (folder && item.parent !== folder) {
            return;
        }
        this.history.push(item);
        this.history.sort(this.historySort);
        this.selectHistory(item.key);
    }
    handleProjectRunDeleted(event) {
        const { history, project } = this;
        if (!project) {
            return;
        }
        if (project.key !== event.project) {
            return;
        }
        const { key } = event;
        const index = history.findIndex(i => i.key === key);
        if (index < 0) {
            return;
        }
        history.splice(index, 1);
        this.requestUpdate();
        if (this.selectedReport === key) {
            this.selectedReport = undefined;
            const [first] = history;
            if (first) {
                this.selectHistory(first.key);
            }
        }
    }
    handleRun() {
        this.execute();
    }
    /**
     * Executes the current configuration.
     */
    async execute() {
        this.lastError = undefined;
        const { config = {}, folder, project } = this;
        if (!project) {
            return;
        }
        if (folder) {
            config.parent = folder;
        }
        // Note, we intentionally don't add shared variables.
        // Project / folder execution should be contained and can only leak 
        // out the variables to the rest of the app, not in.
        // if (sharedVariables && !config.variables) {
        //   config.variables = sharedVariables;
        // }
        const init = {
            kind: HttpProjectKind,
            project: project.toJSON(),
            options: config,
        };
        this.running = true;
        try {
            const result = await CoreEvents.Transport.Core.httpProject(init, this);
            if (!result) {
                this.lastError = 'The project execution event was not handled.';
            }
            else {
                this.mergeExecutedVariables(result.result);
                this.createHistory(result.result);
            }
        }
        catch (e) {
            this.lastError = e.message;
        }
        this.running = false;
    }
    mergeExecutedVariables(result) {
        const { sharedVariables } = this;
        if (!sharedVariables) {
            return;
        }
        result.iterations.forEach((iteration) => {
            const { variables } = iteration;
            if (!variables) {
                return;
            }
            Object.keys(variables).forEach((key) => {
                sharedVariables[key] = variables[key];
            });
        });
    }
    async createHistory(result) {
        const { appInfo, project } = this;
        if (!appInfo) {
            throw new Error(`The appInfo is not set on ${this.localName}`);
        }
        if (!project) {
            return;
        }
        await Events.Store.ProjectRuns.add({
            ...result,
            app: appInfo.code,
            project: project.key,
            key: '',
            kind: ProjectExecutionKind,
            midnight: 0,
            parent: this.folder,
        });
    }
    resetHistory() {
        this.querying = false;
        this.pageCursor = undefined;
        this.history = [];
        if (this.queryPageDebouncer) {
            clearTimeout(this.queryPageDebouncer);
            this.queryPageDebouncer = undefined;
        }
    }
    queryPageDebounce() {
        if (this.queryPageDebouncer) {
            return;
        }
        this.queryPageDebouncer = setTimeout(() => {
            this.queryPageDebouncer = undefined;
            this.queryPage();
        }, this.debounceTimeout);
    }
    async queryPage() {
        const { project, appInfo, folder, pageCursor } = this;
        if (this.querying || !project) {
            return;
        }
        this.querying = true;
        const opts = {
            space: '',
            project: project.key,
        };
        if (appInfo) {
            opts.app = appInfo.code;
        }
        if (folder) {
            opts.parent = folder;
        }
        if (pageCursor) {
            opts.cursor = pageCursor;
        }
        try {
            const result = await Events.Store.ProjectRuns.list(opts, this);
            if (!result) {
                throw new Error(`Project runs query not handled by the store.`);
            }
            const { items, cursor } = result;
            if (items.length) {
                if (cursor) {
                    this.pageCursor = cursor;
                }
                this.history = this.history.concat(items);
                this.history.sort(this.historySort);
                if (!this.selectedReport) {
                    const [last] = this.history;
                    this.selectHistory(last.key);
                }
                this.requestUpdate();
            }
        }
        catch (e) {
            // eslint-disable-next-line no-console
            console.warn(e);
        }
        finally {
            this.querying = false;
        }
    }
    historySort(a, b) {
        return b.ended - a.ended;
    }
    setRecursive(value) {
        const { config = {} } = this;
        config.recursive = value;
        this.config = config;
        this.notifyChange();
    }
    setParallel(value) {
        const { config = {} } = this;
        config.parallel = value;
        this.config = config;
        this.notifyChange();
    }
    setIterations(value) {
        const { config = {} } = this;
        config.iterations = value;
        this.config = config;
        this.notifyChange();
    }
    setIterationDelay(value) {
        const { config = {} } = this;
        config.iterationDelay = value;
        this.config = config;
        this.notifyChange();
    }
    handleRecursiveChecked(e) {
        const input = e.target;
        this.setRecursive(input.checked);
    }
    handleParallelChecked(e) {
        const input = e.target;
        this.setParallel(input.checked);
    }
    handleIterationsChange(e) {
        const input = e.target;
        this.setIterations(input.valueAsNumber);
        input.checkValidity();
    }
    handleIterationDelayChange(e) {
        const input = e.target;
        this.setIterationDelay(input.valueAsNumber);
        input.checkValidity();
    }
    handleHistorySelection(e) {
        const element = e.detail.item;
        const { key } = element.dataset;
        if (!key) {
            return;
        }
        this.selectHistory(key);
    }
    selectHistory(key) {
        this.selectedReport = key;
        const { history } = this;
        const report = history.find(i => i.key === key);
        if (report) {
            this.renderedReport = report;
        }
        else {
            this.renderedReport = undefined;
        }
    }
    render() {
        const { root } = this;
        if (!root) {
            return html ``;
        }
        return html `
    <section id="config">
      <div class="header title-large section-title">Project runner for ${root.info.renderLabel}</div>
      <form name="run-config" class="run-config surface1">
        <div class="section-title title-medium">Run configuration</div>
        ${this.renderRecursiveOption()}
        ${this.renderParallelOption()}
        ${this.renderIterationOption()}
        ${this.renderIterationDelayOption()}
        ${this.renderSubmit()}
      </form>
      ${this.renderError()}
      ${this.renderLoader()}
      ${this.renderReport()}
    </section>
    `;
    }
    renderRecursiveOption() {
        const { config = {} } = this;
        const value = typeof config.recursive === 'boolean' ? config.recursive : false;
        return html `
    <div class="form-row">
      <ui-checkbox 
        id="recursive"
        .checked="${value}"
        name="recursive" 
        aria-labelledby="recursiveLabel"
        aria-describedby="recursiveDescription" 
        @change="${this.handleRecursiveChecked}"
      ></ui-checkbox>
      <label class="label-large" for="recursive" id="recursiveLabel">
        Recursive
      </label>
      <p class="body-medium" id="recursiveDescription">
        Runs all requests from the current folder and its sub-folders
      </p>
    </div>
    `;
    }
    renderParallelOption() {
        const { config = {} } = this;
        const value = typeof config.parallel === 'boolean' ? config.parallel : false;
        return html `
    <div class="form-row">
      <ui-checkbox
        id="parallel"
        .checked="${value}"
        name="parallel" 
        aria-labelledby="parallelLabel"
        aria-describedby="parallelDescription" 
        @change="${this.handleParallelChecked}"
      ></ui-checkbox>
      <label class="label-large" for="parallel" id="parallelLabel">
        Parallel
      </label>
      <p class="body-medium" id="parallelDescription">
        Runs each iteration in parallel. The number of actual execution depends on the number of cores on your device. 
        This is ignored when number of iterations is 1.
      </p>
    </div>
    `;
    }
    renderIterationOption() {
        const { config = {} } = this;
        const value = typeof config.iterations === 'number' ? config.iterations : 1;
        return html `
    <div class="form-row">
      <ui-text-field
        required 
        label="Iterations"
        aria-describedby="iterationsDescription" 
        name="iterations" 
        type="number" 
        min="1" 
        step="1" 
        .value="${String(value)}"
        @change="${this.handleIterationsChange}"
      ></ui-text-field>
      <p class="body-medium" id="iterationsDescription">
        The number of times the execution should be repeated.
      </p>
    </div>
    `;
    }
    renderIterationDelayOption() {
        const { config = {} } = this;
        const value = typeof config.iterationDelay === 'number' ? config.iterationDelay : 0;
        return html `
    <div class="form-row">
      <ui-text-field
        required
        label="Iteration delay"
        aria-describedby="iterationDelayDescription"
        name="iterationDelay"
        type="number"
        min="0"
        step="1"
        .value="${String(value)}"
        @change="${this.handleIterationDelayChange}"
      ></ui-text-field>
      <p class="body-medium" id="iterationDelayDescription">
        The number of milliseconds to wait between each iteration.
      </p>
    </div>
    `;
    }
    renderSubmit() {
        return html `
    <div class="submit-row">
      <ui-button type="filled" @click="${this.handleRun}" ?disabled="${this.running}">Run</ui-button>
    </div>
    `;
    }
    renderLoader() {
        if (!this.running) {
            return '';
        }
        return html `
    <ui-progress indeterminate></ui-progress>
    `;
    }
    renderError() {
        const { lastError } = this;
        if (!lastError) {
            return '';
        }
        return html `<div class="error on-error-text body-large last-error">${lastError}</div>`;
    }
    renderReport() {
        const { renderedReport, history } = this;
        if (!history.length) {
            return nothing;
        }
        return html `
    <section aria-label="Latest report" class="report surface1">
      <div class="section-title title-medium">Run reports</div>
      ${this.renderHistorySelector()}
      <project-run-report .report="${renderedReport}"></project-run-report>
    </section>
    `;
    }
    renderHistorySelector() {
        const { history } = this;
        const disabled = !history.length;
        const selected = history.find(i => i.key === this.selectedReport);
        return html `
    <div class="history-selector">
      <ui-dropdown-list
        verticalAlign="top"
        class="history-dropdown"
        closeOnOutsideClick
        @select="${this.handleHistorySelection}"
      >
        <ui-button type="tonal" ?disabled="${disabled}">
          <ui-icon slot="icon" icon="arrowDropDown"></ui-icon>
          ${selected ? html `<relative-time datetime="${new Date(selected.ended).toISOString()}"></relative-time>` : 'History'}
        </ui-button>
        <ui-list slot="dropdown" role="menu" aria-label="History list">
          ${history.map(current => this.renderHistoryOption(current))}
        </ui-list>
      </ui-dropdown-list>
    </div>
    `;
    }
    renderHistoryOption(item) {
        const { ended, key } = item;
        const classes = {
            'history-option': true,
            highlight: this.selectedReport === key,
        };
        const de = new Date(ended);
        return html `
    <ui-list-item role="menuitem" data-key="${key}" class="${classMap(classes)}">
      <relative-time datetime="${de.toISOString()}"></relative-time>
    </ui-list-item>
    `;
    }
}
__decorate([
    property({ type: Object })
], ProjectRunner.prototype, "sharedVariables", void 0);
__decorate([
    property({ type: Object })
], ProjectRunner.prototype, "project", void 0);
__decorate([
    property({ type: Object })
], ProjectRunner.prototype, "appInfo", void 0);
__decorate([
    property({ type: String, reflect: true })
], ProjectRunner.prototype, "folder", void 0);
__decorate([
    state()
], ProjectRunner.prototype, "root", void 0);
__decorate([
    state()
], ProjectRunner.prototype, "running", void 0);
__decorate([
    state()
], ProjectRunner.prototype, "querying", void 0);
__decorate([
    property({ type: Object })
], ProjectRunner.prototype, "config", void 0);
__decorate([
    state()
], ProjectRunner.prototype, "lastError", void 0);
__decorate([
    state()
], ProjectRunner.prototype, "renderedReport", void 0);
//# sourceMappingURL=ProjectRunner.js.map