var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
    if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
    if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
    return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
    if (kind === "m") throw new TypeError("Private method is not writable");
    if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
    if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
    return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var _PlayEditor_editor, _PlayEditor_dispatchThrottledSourceChangedEvent;
import { autocompletion, completeFromList } from '@codemirror/autocomplete';
import { tsxLanguage } from '@codemirror/lang-javascript';
import { linter } from '@codemirror/lint';
import { EditorState } from '@codemirror/state';
import { hoverTooltip } from '@codemirror/view';
import { consume } from '@lit-labs/context';
import * as tsvfs from '@typescript/vfs';
import { EditorView, basicSetup } from 'codemirror';
import { LitElement, css, html } from 'lit';
import { customElement, eventOptions, property, query, queryAssignedElements } from 'lit/decorators.js';
import ts, { displayPartsToString } from 'typescript';
import { appEntrypointFilename } from '../../bundler/compiler.js';
import { throttle } from '../../utils/throttle.js';
import { unindent } from '../../utils/unindent.js';
import { Bubble } from '../bubble.js';
import { penCtx } from './play-pen-context.js';
/** Accepts a slotted template. */
let PlayEditor = class PlayEditor extends LitElement {
    constructor() {
        super(...arguments);
        _PlayEditor_editor.set(this, void 0);
        _PlayEditor_dispatchThrottledSourceChangedEvent.set(this, throttle((src) => {
            this.dispatchEvent(Bubble('play-pen-set-src', src));
        }, 500));
    }
    setSrc(src) {
        __classPrivateFieldGet(this, _PlayEditor_editor, "f").dispatch({
            changes: { from: 0, to: __classPrivateFieldGet(this, _PlayEditor_editor, "f").state.doc.length, insert: src }
        });
    }
    firstUpdated() {
        const init = EditorState.create({
            doc: this.src ?? this.template ?? '',
            extensions: [
                basicSetup,
                tsxLanguage,
                EditorView.lineWrapping,
                linter((_view) => {
                    const diagnostics = this.env.languageService.getSemanticDiagnostics(appEntrypointFilename);
                    return diagnostics.map(tsDiagnosticToMirrorDiagnostic);
                }),
                tsAutocomplete(this.env),
                tsHoverTip(this.env)
            ]
        });
        __classPrivateFieldSet(this, _PlayEditor_editor, new EditorView({
            dispatch: transaction => {
                __classPrivateFieldGet(this, _PlayEditor_editor, "f").update([transaction]);
                if (transaction.docChanged) {
                    const src = transaction.state.doc.sliceString(0);
                    __classPrivateFieldGet(this, _PlayEditor_dispatchThrottledSourceChangedEvent, "f").call(this, src);
                }
            },
            parent: this._root,
            state: init
        }), "f");
    }
    render() {
        return html `<div class="editor"></div>
      <slot @slotchange=${this._onSlotChange} />`;
    }
    _onSlotChange() {
        // If <script /> exists, get the program inside.
        const src = unindent(this._scripts[0]?.innerText ?? '');
        // The slotted template has the lowest precedence. Do not overwrite
        // any existing template.
        if (this.template == null)
            this.dispatchEvent(Bubble('play-pen-set-template', src));
        // If no source was restored, use the template.
        if (this.src != null)
            return;
        this.setSrc(src);
        this.dispatchEvent(Bubble('play-pen-set-src', src));
    }
};
_PlayEditor_editor = new WeakMap();
_PlayEditor_dispatchThrottledSourceChangedEvent = new WeakMap();
PlayEditor.styles = css `
    .editor {
      height: 100%;
      width: 100%;
    }
    .cm-editor {
      height: 100%;
      overflow: hidden;
    }
    .cm-editor.cm-focused {
      outline: none;
    }
    .cm-editor .cm-gutters {
      background-color: var(--rpl-neutral-background);
      color: var(--rpl-neutral-content-weak);
      border-right: none;
    }
    .cm-editor .cm-lineNumbers .cm-activeLineGutter {
      background-color: var(--rpl-orangered-100);
      border-top-left-radius: 4px;
      border-bottom-left-radius: 4px;
    }
    .cm-editor .cm-foldGutter .cm-activeLineGutter {
      background-color: var(--rpl-orangered-100);
    }
    .cm-editor .cm-activeLine {
      background-color: var(--rpl-orangered-100);
      border-top-right-radius: 4px;
      border-bottom-right-radius: 4px;
    }
  `;
__decorate([
    consume({ context: penCtx.env }),
    property({ attribute: false })
], PlayEditor.prototype, "env", void 0);
__decorate([
    consume({ context: penCtx.src }),
    property({ attribute: false })
], PlayEditor.prototype, "src", void 0);
__decorate([
    consume({ context: penCtx.template }),
    property({ attribute: false })
], PlayEditor.prototype, "template", void 0);
__decorate([
    query('div')
], PlayEditor.prototype, "_root", void 0);
__decorate([
    queryAssignedElements({
        flatten: true,
        selector: 'script[type="application/devvit"]'
    })
], PlayEditor.prototype, "_scripts", void 0);
__decorate([
    eventOptions({ once: true })
], PlayEditor.prototype, "_onSlotChange", null);
PlayEditor = __decorate([
    customElement('play-editor')
], PlayEditor);
export { PlayEditor };
function tsDiagnosticToMirrorDiagnostic(diagnostic) {
    const severity = {
        [ts.DiagnosticCategory.Error]: 'error',
        [ts.DiagnosticCategory.Warning]: 'warning',
        [ts.DiagnosticCategory.Suggestion]: 'info',
        [ts.DiagnosticCategory.Message]: 'info'
    }[diagnostic.category];
    return {
        from: diagnostic.start ?? 0,
        to: (diagnostic.start ?? 0) + (diagnostic.length ?? 0),
        severity,
        message: String(diagnostic.messageText),
        source: diagnostic.source ?? ''
    };
}
function tsHoverTip(env) {
    return hoverTooltip((_editor, pos) => {
        // to-do: clean up.
        console.log('hover');
        const info = env.languageService.getQuickInfoAtPosition(appEntrypointFilename, pos);
        if (!info)
            return null;
        console.log('hover with info', info);
        return {
            pos: info.textSpan.start,
            end: info.textSpan.start + info.textSpan.length,
            create() {
                const dom = document.createElement('div');
                dom.className = 'cm-quickinfo-tooltip';
                dom.textContent =
                    displayPartsToString(info.displayParts) +
                        (info.documentation?.length
                            ? '\n' + displayPartsToString(info.documentation)
                            : '');
                return { dom };
            }
        };
    });
}
function tsAutocomplete(env) {
    return autocompletion({
        override: [
            ctx => {
                const completions = env.languageService.getCompletionsAtPosition(appEntrypointFilename, ctx.pos, {
                // to-do: explore the many completion options here.
                });
                if (!completions)
                    return null;
                return completeFromList(completions.entries.map(completion => ({
                    type: completion.kind,
                    label: completion.name,
                    detail: completion.labelDetails?.detail ?? ''
                })))(ctx);
            }
        ]
    });
}
