import { Kernel, KernelManager, KernelMessage } from "@jupyterlab/services";
import { JupyterPluginSettings } from "../types";

import * as P from "@jupyterlab/services/lib/serverconnection";
import { IKernelConnection } from "@jupyterlab/services/lib/kernel/kernel";
import { OutputArea } from "@jupyterlab/outputarea";
import { customElement } from "lit/decorators/custom-element";

const lit = window.runtime.exports.libraries.lit;
const html = lit.html;

@customElement("starboard-jupyter-manager")
export class StarboardJupyterManager extends lit.LitElement {
  private settings: JupyterPluginSettings;
  private manager: KernelManager;

  private isReady = false;
  private runningKernels: Kernel.IModel[] = [];

  private currentKernel?: IKernelConnection;
  private connectionError: Error | undefined;

  constructor(jupyterSettings: JupyterPluginSettings) {
    super();
    this.settings = jupyterSettings;

    this.manager = new KernelManager({
      standby: "when-hidden",
      serverSettings: P.ServerConnection.makeSettings(jupyterSettings.serverSettings),
    });

    this.manager.connectionFailure.connect((km, err) => {
      console.warn("Jupyter Connection Failure", err);
      this.connectionError = err;
      this.performUpdate();
    }, this);

    this.manager.ready.then(
      () => {
        console.log("Jupyter manager is now ready");
        this.isReady = true;
        this.performUpdate();
      },
      (err) => {
        console.warn("Jupyter manager failed to ready", err);
        this.isReady = false;
        this.connectionError = err;
        this.performUpdate();
      }
    );

    this.manager.runningChanged.connect((km, running) => {
      this.runningKernels = running;
      this.connectionError = undefined;
      this.performUpdate();
    }, this);
  }

  private setupKernelConnection() {
    this.currentKernel!.statusChanged.connect((kc, status) => {
      if (status === "dead" && this.currentKernel) {
        this.currentKernel.dispose();
        this.currentKernel = undefined;
      }
      this.performUpdate();
    });
    this.currentKernel!.connectionStatusChanged.connect((kc, status) => {
      this.performUpdate();
    });
    this.manager.refreshRunning().catch((e) => console.error("Failed to refresh running kernels:", e));
  }

  createRenderRoot() {
    return this;
  }

  async startKernel(name?: string, shutdownCurrentKernel?: boolean) {
    if (shutdownCurrentKernel && this.currentKernel && !this.currentKernel.isDisposed) {
      console.error("Already connected to a kernel, shutting down existing kernel");
      await this.currentKernel.shutdown();
      this.currentKernel.dispose();
    }
    this.currentKernel = await this.manager.startNew({ name: name });
    this.setupKernelConnection();
    this.performUpdate();
  }

  async connectToKernel(id: string) {
    if (this.currentKernel && !this.currentKernel.isDisposed) {
      this.currentKernel.dispose();
      this.currentKernel = undefined;
    }

    this.currentKernel = this.manager.connectTo({ model: { name: "", id } });
    this.setupKernelConnection();
    this.performUpdate();
  }

  async shutdownKernel(id: string) {
    this.manager.shutdown(id);
  }

  async interruptKernel() {
    if (this.currentKernel) {
      this.currentKernel.interrupt();
    }
  }

  async disconnectFromKernel() {
    if (this.currentKernel) {
      this.currentKernel.dispose();
      this.currentKernel = undefined;
      this.performUpdate();
    }
  }

  /**
   * Takes an object with a `code` field.
   * There are more parameters which you probably won't need.
   */
  async runCode(content: KernelMessage.IExecuteRequestMsg["content"], output: OutputArea) {
    if (!this.currentKernel) {
      await this.startKernel();
    }

    output.future = this.currentKernel!.requestExecute(content);
  }

  disconnectedCallback() {
    super.disconnectedCallback();

    (async () => {
      if (this.currentKernel) {
        await this.manager.shutdown(this.currentKernel.id);
      }
      this.manager.dispose();
    })();
  }

  render() {
    return html`
      <section class="starboard-jupyter-interface py-2 px-3 my-2">
        <details>
          <summary class="d-flex justify-content-between flex-wrap">
            <div class="d-flex align-items-center flex-wrap">
              ${this.settings.headerText ? html`<h2 class="h5 mb-0 me-2">${this.settings.headerText}</h2>` : undefined}
              ${this.connectionError
                ? html`<div class="badge bg-danger" style="width: max-content">Connection Error</div>`
                : this.isReady
                ? html`<div class="badge bg-success small" style="width: max-content">✅ OK</div>`
                : html`<div class="badge bg-light text-dark" style="width: max-content">Connecting to Jupyter..</div>`}
            </div>
            <div>
              ${this.currentKernel
                ? html` <span
                      class="badge ${this.currentKernel.connectionStatus === "connected"
                        ? "bg-success"
                        : "bg-warning text-dark"}"
                    >
                      ${this.currentKernel.connectionStatus}
                    </span>
                    <span title="Kernel Status" class="badge bg-dark"> ${this.currentKernel.status} </span>
                    <button
                      @click=${() => this.interruptKernel()}
                      title="Interrupt Kernel"
                      class="btn btn-outline-secondary btn-sm btn-rounded py-0"
                    >
                      Interrupt
                    </button>`
                : html`<span class="badge bg-light text-dark">Not connected to a kernel</span>`}
            </div>
          </summary>
          ${this.isReady
            ? html` ${
                  // this.connectionError ?
                  // html`<button @click=${() => this.attemptReconnect()} class="ms-3 mt-2 btn btn-sm btn-outline-primary">Force Retry Connection</button>` :
                  html`<button @click=${() => this.startKernel()} class="mt-2 btn btn-sm btn-outline-primary">
                    Start new Kernel
                  </button>`
                }
                <ul class="list-group m-3">
                  ${this.runningKernels.map((v) => {
                    if (this.currentKernel && this.currentKernel.id === v.id) {
                      return html`<li
                        class="list-group-item bg-light text-dark list-group-item-action d-flex justify-content-between align-items-center"
                      >
                        <span>🔗 <b>${v.name}</b> <code>${v.id}</code></span>

                        <div class="d-flex align-items-center">
                          <button
                            @click=${() => this.disconnectFromKernel()}
                            class="btn btn-sm btn-outline-secondary me-2 text-dark bg-white"
                          >
                            Disconnect
                          </button>
                          <!-- <button @click=${() =>
                            this.shutdownKernel(
                              v.id
                            )} class="btn btn-sm btn-outline-secondary me-2 text-dark">Shut Down</button> -->
                          <span
                            title="Last Activity: ${(v as any).last_activity}"
                            class="badge bg-primary rounded-pill"
                          >
                            ${this.currentKernel.status}
                          </span>
                        </div>
                      </li>`;
                    } else {
                      return html`<div class="list-group-item list-group-item-action d-flex justify-content-between align-items-center">
                                            <span><b>${v.name}</b> <code>${v.id}</code></span>
                        <div class="d-flex align-items-center">
                            <button @click=${() =>
                              this.connectToKernel(v.id)} class="btn btn-sm btn-outline-primary me-2">Connect</button>  
                            <button @click=${() =>
                              this.shutdownKernel(v.id)} class="btn btn-sm btn-outline-primary me-2">Shut Down</button>
                            <span title="Last Activity: ${
                              (v as any).last_activity
                            }" class="badge bg-primary rounded-pill">
                                ${(v as any).execution_state}
                            </span>
                        </div>
                    </div>
                </div>`;
                    }
                  })}
                </ul>`
            : undefined}
          ${this.connectionError
            ? html` <div class="alert alert-danger mt-2">
                <b>Connection Error</b>
                <p>${this.connectionError}</p>
                <br />
                <p class="small">Check the Network tab in your browser's developer console for more details.</p>
              </div>`
            : undefined}
        </details>
      </section>
    `;
  }
}
