import {
  DeesElement,
  property,
  html,
  customElement,
  type TemplateResult,
  css,
  cssManager,
  state,
} from '@design.estate/dees-element';
import * as domtools from '@design.estate/dees-domtools';

import type { Terminal } from 'xterm';
import { themeDefaultStyles } from '../../00theme.js';
import type { IExecutionEnvironment } from '../../00group-runtime/index.js';
import { WebContainerEnvironment } from '../../00group-runtime/index.js';
import '../../dees-icon/dees-icon.js';
import '../../dees-actionbar/dees-actionbar.js';
import type { DeesActionbar } from '../../dees-actionbar/dees-actionbar.js';
import { TerminalTabManager } from './terminal-tab-manager.js';
import type {
  ITerminalTab,
  ICreateTerminalTabOptions,
  TTerminalTabType,
} from './interfaces.js';
import { DeesServiceLibLoader } from '../../../services/index.js';

declare global {
  interface HTMLElementTagNameMap {
    'dees-workspace-terminal': DeesWorkspaceTerminal;
  }
}

@customElement('dees-workspace-terminal')
export class DeesWorkspaceTerminal extends DeesElement {
  public static demo = () => {
    const env = new WebContainerEnvironment();
    return html`<dees-workspace-terminal .executionEnvironment=${env}></dees-workspace-terminal>`;
  };
  public static demoGroup = 'Workspace';

  // INSTANCE
  private resizeObserver: ResizeObserver;
  private tabManager: TerminalTabManager;

  /**
   * The execution environment (required).
   * Use WebContainerEnvironment for browser-based execution.
   */
  @property({ type: Object })
  accessor executionEnvironment: IExecutionEnvironment | null = null;

  @property()
  accessor setupCommand = '';

  /**
   * Environment variables to set in the shell
   */
  @property()
  accessor environmentVariables: { [key: string]: string } = {};

  /**
   * Width of the tab bar in pixels
   */
  @property({ type: Number })
  accessor tabBarWidth: number = 160;

  /**
   * Whether to show the tab bar
   */
  @property({ type: Boolean })
  accessor showTabBar: boolean = true;

  // Tab state
  @state()
  accessor tabs: ITerminalTab[] = [];

  @state()
  accessor activeTabId: string | null = null;

  // Theme subscription for dynamic theme updates
  private terminalThemeSubscription: any = null;
  private isBright: boolean = false;

  // Actionbar reference for terminal-context notifications
  private terminalActionbar: DeesActionbar | null = null;

  /**
   * Promise that resolves when the environment is ready.
   * @deprecated Use executionEnvironment directly
   */
  private environmentDeferred = new domtools.plugins.smartpromise.Deferred<IExecutionEnvironment>();
  public environmentPromise = this.environmentDeferred.promise;

  constructor() {
    super();
    this.tabManager = new TerminalTabManager();
    this.resizeObserver = new ResizeObserver(() => {
      this.handleResize();
    });
  }

  public static styles = [
    themeDefaultStyles,
    cssManager.defaultStyles,
    css`
      :host {
        background: ${cssManager.bdTheme('#ffffff', '#000000')};
        position: absolute;
        height: 100%;
        width: 100%;
        display: flex;
        flex-direction: row;
      }

      * {
        box-sizing: border-box;
      }

      .terminal-container {
        display: flex;
        flex-direction: row;
        width: 100%;
        height: 100%;
      }

      .terminal-content {
        flex: 1;
        display: flex;
        flex-direction: column;
        overflow: hidden;
        background: ${cssManager.bdTheme('#ffffff', '#000000')};
      }

      #active-terminal-container {
        flex: 1;
        position: relative;
        min-height: 0;
        margin: 20px;
      }

      .terminal-content dees-actionbar {
        flex-shrink: 0;
      }

      /* Tab bar on the right side */
      .tab-bar {
        display: flex;
        flex-direction: column;
        background: ${cssManager.bdTheme('hsl(0 0% 96%)', 'hsl(0 0% 7%)')};
        border-left: 1px solid ${cssManager.bdTheme('hsl(0 0% 85%)', 'hsl(0 0% 15%)')};
        flex-shrink: 0;
        overflow: hidden;
      }

      .tab-bar-header {
        display: flex;
        align-items: center;
        justify-content: space-between;
        padding: 8px 10px;
        border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 88%)', 'hsl(0 0% 12%)')};
        font-size: 11px;
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.5px;
        color: ${cssManager.bdTheme('hsl(0 0% 45%)', 'hsl(0 0% 55%)')};
      }

      .tab-bar-title {
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
      }

      .tab-bar-actions {
        display: flex;
        gap: 2px;
      }

      .tab-action {
        width: 22px;
        height: 22px;
        display: flex;
        align-items: center;
        justify-content: center;
        border-radius: 4px;
        cursor: pointer;
        color: ${cssManager.bdTheme('hsl(0 0% 50%)', 'hsl(0 0% 60%)')};
        transition: all 0.15s ease;
      }

      .tab-action:hover {
        background: ${cssManager.bdTheme('hsl(0 0% 88%)', 'hsl(0 0% 15%)')};
        color: ${cssManager.bdTheme('hsl(0 0% 30%)', 'hsl(0 0% 80%)')};
      }

      .tab-list {
        flex: 1;
        overflow-y: auto;
        padding: 4px;
      }

      .terminal-tab {
        display: flex;
        align-items: center;
        gap: 8px;
        padding: 8px 10px;
        margin-bottom: 2px;
        border-radius: 6px;
        cursor: pointer;
        font-size: 12px;
        color: ${cssManager.bdTheme('hsl(0 0% 45%)', 'hsl(0 0% 60%)')};
        transition: all 0.15s ease;
      }

      .terminal-tab:hover {
        background: ${cssManager.bdTheme('hsl(0 0% 90%)', 'hsl(0 0% 12%)')};
        color: ${cssManager.bdTheme('hsl(0 0% 25%)', 'hsl(0 0% 80%)')};
      }

      .terminal-tab.active {
        background: ${cssManager.bdTheme('hsl(210 100% 95%)', 'hsl(210 30% 15%)')};
        color: ${cssManager.bdTheme('hsl(210 100% 40%)', 'hsl(210 100% 70%)')};
      }

      .terminal-tab.exited {
        opacity: 0.7;
      }

      .tab-icon {
        flex-shrink: 0;
      }

      .tab-icon.running {
        color: ${cssManager.bdTheme('hsl(142 70% 40%)', 'hsl(142 70% 55%)')};
      }

      .tab-label {
        flex: 1;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
      }

      .exit-badge {
        font-size: 10px;
        padding: 1px 5px;
        border-radius: 8px;
        font-weight: 600;
        flex-shrink: 0;
      }

      .exit-badge.success {
        background: ${cssManager.bdTheme('hsl(142 70% 90%)', 'hsl(142 30% 20%)')};
        color: ${cssManager.bdTheme('hsl(142 70% 35%)', 'hsl(142 70% 60%)')};
      }

      .exit-badge.error {
        background: ${cssManager.bdTheme('hsl(0 70% 93%)', 'hsl(0 30% 20%)')};
        color: ${cssManager.bdTheme('hsl(0 70% 45%)', 'hsl(0 70% 60%)')};
      }

      .tab-close {
        width: 18px;
        height: 18px;
        display: flex;
        align-items: center;
        justify-content: center;
        border-radius: 4px;
        opacity: 0;
        transition: all 0.15s ease;
        flex-shrink: 0;
      }

      .terminal-tab:hover .tab-close {
        opacity: 0.6;
      }

      .tab-close:hover {
        opacity: 1 !important;
        background: ${cssManager.bdTheme('hsl(0 0% 85%)', 'hsl(0 0% 25%)')};
        color: ${cssManager.bdTheme('hsl(0 70% 50%)', 'hsl(0 70% 60%)')};
      }

      .empty-state {
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        height: 100%;
        color: ${cssManager.bdTheme('hsl(0 0% 50%)', 'hsl(0 0% 50%)')};
        font-size: 13px;
        gap: 8px;
      }

      /* xterm.js styles */
      .xterm {
        font-feature-settings: 'liga' 0;
        position: relative;
        user-select: none;
        -ms-user-select: none;
        -webkit-user-select: none;
      }

      .xterm.focus,
      .xterm:focus {
        outline: none;
      }

      .xterm .xterm-helpers {
        position: absolute;
        top: 0;
        z-index: 5;
      }

      .xterm .xterm-helper-textarea {
        padding: 0;
        border: 0;
        margin: 0;
        position: absolute;
        opacity: 0;
        left: -9999em;
        top: 0;
        width: 0;
        height: 0;
        z-index: -5;
        white-space: nowrap;
        overflow: hidden;
        resize: none;
      }

      .xterm .composition-view {
        background: ${cssManager.bdTheme('#ffffff', '#000000')};
        color: ${cssManager.bdTheme('#333333', '#ffffff')};
        display: none;
        position: absolute;
        white-space: nowrap;
        z-index: 1;
      }

      .xterm .composition-view.active {
        display: block;
      }

      .xterm .xterm-viewport {
        background-color: ${cssManager.bdTheme('#ffffff', '#000000')};
        overflow-y: scroll;
        cursor: default;
        position: absolute;
        right: 0;
        left: 0;
        top: 0;
        bottom: 0;
      }

      .xterm .xterm-screen {
        position: relative;
      }

      .xterm .xterm-screen canvas {
        position: absolute;
        left: 0;
        top: 0;
      }

      .xterm .xterm-scroll-area {
        visibility: hidden;
      }

      .xterm-char-measure-element {
        display: inline-block;
        visibility: hidden;
        position: absolute;
        top: 0;
        left: -9999em;
        line-height: normal;
      }

      .xterm {
        cursor: text;
      }

      .xterm.enable-mouse-events {
        cursor: default;
      }

      .xterm.xterm-cursor-pointer {
        cursor: pointer;
      }

      .xterm.column-select.focus {
        cursor: crosshair;
      }

      .xterm .xterm-accessibility,
      .xterm .xterm-message {
        position: absolute;
        left: 0;
        top: 0;
        bottom: 0;
        right: 0;
        z-index: 10;
        color: transparent;
      }

      .xterm .live-region {
        position: absolute;
        left: -9999px;
        width: 1px;
        height: 1px;
        overflow: hidden;
      }

      .xterm-dim {
        opacity: 0.5;
      }

      .xterm-underline {
        text-decoration: underline;
      }
    `,
  ];

  public render(): TemplateResult {
    const activeTab = this.activeTabId ? this.tabManager.getTab(this.activeTabId) : null;

    return html`
      <div class="terminal-container">
        <!-- Main terminal area -->
        <div class="terminal-content">
          ${activeTab ? html`
            <div id="active-terminal-container"></div>
          ` : html`
            <div class="empty-state">
              <dees-icon .icon=${'lucide:terminal'} iconSize="32"></dees-icon>
              <span>No terminal open</span>
            </div>
          `}
          <dees-actionbar></dees-actionbar>
        </div>

        <!-- Vertical tab bar on the right -->
        ${this.showTabBar ? html`
          <div class="tab-bar" style="width: ${this.tabBarWidth}px">
            <div class="tab-bar-header">
              <span class="tab-bar-title">Terminals</span>
              <div class="tab-bar-actions">
                <div class="tab-action" @click=${this.handleAddTab} title="New Terminal">
                  <dees-icon .icon=${'lucide:plus'} iconSize="14"></dees-icon>
                </div>
              </div>
            </div>

            <div class="tab-list">
              ${this.tabs.map(tab => html`
                <div
                  class="terminal-tab ${tab.id === this.activeTabId ? 'active' : ''} ${tab.exited ? 'exited' : ''}"
                  @click=${() => this.switchToTab(tab.id)}
                >
                  <dees-icon
                    .icon=${tab.iconName}
                    iconSize="14"
                    class="tab-icon ${tab.process && !tab.exited ? 'running' : ''}"
                  ></dees-icon>
                  <span class="tab-label">${tab.label}</span>
                  ${tab.exited ? html`
                    <span class="exit-badge ${tab.exitCode === 0 ? 'success' : 'error'}">
                      ${tab.exitCode}
                    </span>
                  ` : ''}
                  ${tab.closeable ? html`
                    <span class="tab-close" @click=${(e: Event) => this.handleTabClose(e, tab.id)}>
                      <dees-icon .icon=${'lucide:x'} iconSize="12"></dees-icon>
                    </span>
                  ` : ''}
                </div>
              `)}
            </div>
          </div>
        ` : ''}
      </div>
    `;
  }

  public async firstUpdated(): Promise<void> {
    const domtoolsInstance = await this.domtoolsPromise;

    // Get current theme
    this.isBright = domtoolsInstance.themeManager.goBrightBoolean;

    // Subscribe to theme changes
    this.terminalThemeSubscription = domtoolsInstance.themeManager.themeObservable.subscribe(
      (goBright: boolean) => {
        this.isBright = goBright;
        this.tabManager.updateAllThemes(goBright);
      }
    );

    // Load xterm from CDN
    const libLoader = DeesServiceLibLoader.getInstance();
    const [xtermBundle, fitAddonBundle] = await Promise.all([
      libLoader.loadXterm(),
      libLoader.loadXtermFitAddon(),
    ]);

    // Initialize tab manager with loaded modules
    this.tabManager.setXtermModules(xtermBundle, fitAddonBundle);

    // Create default shell tab
    await this.createShellTab();
  }

  async connectedCallback(): Promise<void> {
    await super.connectedCallback();
    // ResizeObserver is set up in attachTerminalToContainer when the container exists
  }

  async disconnectedCallback(): Promise<void> {
    // Unobserve the terminal container
    const container = this.shadowRoot?.getElementById('active-terminal-container');
    if (container) {
      this.resizeObserver.unobserve(container);
    }
    if (this.terminalThemeSubscription) {
      this.terminalThemeSubscription.unsubscribe();
      this.terminalThemeSubscription = null;
    }
    this.tabManager.disposeAll();
    await super.disconnectedCallback();
  }

  // ========== Tab Management ==========

  private async handleAddTab(): Promise<void> {
    await this.createShellTab();
  }

  private handleTabClose(e: Event, tabId: string): void {
    e.stopPropagation();
    this.closeTab(tabId);
  }

  private switchToTab(tabId: string): void {
    if (tabId === this.activeTabId) return;

    const tab = this.tabManager.getTab(tabId);
    if (!tab) return;

    const previousTabId = this.activeTabId;

    // Detach current terminal from DOM
    if (this.activeTabId) {
      this.detachActiveTerminal();
    }

    // Update active tab
    this.activeTabId = tabId;
    this.tabs = this.tabManager.getAllTabs();

    // Wait for render then attach new terminal
    this.updateComplete.then(() => {
      this.attachTerminalToContainer(tab);

      // Dispatch event
      this.dispatchEvent(
        new CustomEvent('tab-switched', {
          bubbles: true,
          composed: true,
          detail: { tabId, previousTabId },
        })
      );
    });
  }

  private detachActiveTerminal(): void {
    const container = this.shadowRoot?.getElementById('active-terminal-container');
    if (container) {
      container.innerHTML = '';
    }
  }

  private attachTerminalToContainer(tab: ITerminalTab): void {
    const container = this.shadowRoot?.getElementById('active-terminal-container');
    if (!container) return;

    // Observe container for resize (handles actionbar appearing/disappearing)
    // ResizeObserver.observe() is idempotent - safe to call multiple times
    this.resizeObserver.observe(container);

    // Clear container
    container.innerHTML = '';

    // Open terminal in container
    tab.terminal.open(container);
    tab.fitAddon.fit();
    tab.terminal.focus();
  }

  private handleResize(): void {
    if (this.activeTabId) {
      const tab = this.tabManager.getTab(this.activeTabId);
      if (tab) {
        tab.fitAddon.fit();
      }
    }
  }

  // ========== Process Management ==========

  private async spawnProcessForTab(
    tab: ITerminalTab,
    command: string,
    args: string[] = []
  ): Promise<void> {
    if (!this.executionEnvironment) {
      tab.terminal.write('\x1b[31m');
      tab.terminal.write('❌ No execution environment available.\r\n');
      tab.terminal.write('\x1b[0m');
      return;
    }

    try {
      // Initialize environment if needed
      if (!this.executionEnvironment.ready) {
        tab.terminal.write('Initializing environment...\r\n');
        await this.executionEnvironment.init();
      }

      // Spawn process
      const process = await this.executionEnvironment.spawn(command, args);

      // Set up output piping
      process.output.pipeTo(
        new WritableStream({
          write: (data) => {
            tab.terminal.write(data);
          },
        })
      );

      // Set up input for interactive processes
      const inputWriter = process.input.getWriter();
      tab.terminal.onData((data) => {
        inputWriter.write(data);
      });

      // Store process reference
      this.tabManager.setTabProcess(tab.id, process, inputWriter);
      this.tabs = this.tabManager.getAllTabs();

      // Handle process exit
      process.exit.then((exitCode) => {
        this.handleProcessExit(tab.id, exitCode);
      });
    } catch (error) {
      tab.terminal.write('\x1b[31m');
      tab.terminal.write(`❌ Failed to spawn process: ${error}\r\n`);
      tab.terminal.write('\x1b[0m');
      console.error('Failed to spawn process:', error);
    }
  }

  private handleProcessExit(tabId: string, exitCode: number): void {
    const tab = this.tabManager.getTab(tabId);
    if (!tab) return;

    // Update tab state
    this.tabManager.markTabExited(tabId, exitCode);

    // Write exit message to terminal
    const message =
      exitCode === 0
        ? '\r\n\x1b[32m[Process completed successfully]\x1b[0m\r\n'
        : `\r\n\x1b[31m[Process exited with code ${exitCode}]\x1b[0m\r\n`;
    tab.terminal.write(message);

    // Update state to trigger re-render
    this.tabs = this.tabManager.getAllTabs();

    // Dispatch event
    this.dispatchEvent(
      new CustomEvent('process-complete', {
        bubbles: true,
        composed: true,
        detail: { tabId, exitCode },
      })
    );

    // Show actionbar to offer closing the tab (only if tab is closeable)
    if (tab.closeable) {
      this.showExitedTabActionbar(tabId, tab.label, exitCode);
    }
  }

  /**
   * Show actionbar offering to close an exited tab
   */
  private async showExitedTabActionbar(tabId: string, tabLabel: string, exitCode: number): Promise<void> {
    const isSuccess = exitCode === 0;
    const result = await this.showActionbar({
      message: isSuccess
        ? `"${tabLabel}" completed. Close tab?`
        : `"${tabLabel}" exited (code ${exitCode}). Close tab?`,
      type: isSuccess ? 'info' : 'warning',
      icon: isSuccess ? 'lucide:checkCircle' : 'lucide:alertTriangle',
      actions: [
        { id: 'close', label: 'Close Tab', primary: true },
        { id: 'keep', label: 'Keep Open' },
      ],
      timeout: { duration: 10000, defaultActionId: 'close' },
      dismissible: true,
    });

    // Close tab if user clicked "Close Tab" or timeout triggered auto-close
    if (result.actionId === 'close') {
      this.closeTab(tabId);
    }
  }

  // ========== Public API ==========

  /**
   * Create a new shell tab
   */
  public async createShellTab(label?: string): Promise<string> {
    const tab = this.tabManager.createTab(
      {
        type: 'shell',
        label: label || `bash ${this.tabManager.getTabCount() + 1}`,
        closeable: this.tabManager.getTabCount() > 0, // First tab not closeable
      },
      this.isBright
    );

    this.tabs = this.tabManager.getAllTabs();

    // Switch to new tab
    this.switchToTab(tab.id);

    // Wait for DOM update then spawn shell
    await this.updateComplete;
    await this.spawnProcessForTab(tab, 'jsh');

    // Run setup command if this is the first tab
    if (this.tabManager.getTabCount() === 1 && this.setupCommand) {
      await this.waitForPrompt(tab.terminal, '~/');
      if (tab.inputWriter) {
        tab.inputWriter.write(this.setupCommand);
      }
    }

    // Dispatch event
    this.dispatchEvent(
      new CustomEvent('tab-created', {
        bubbles: true,
        composed: true,
        detail: { tabId: tab.id },
      })
    );

    return tab.id;
  }

  /**
   * Create a terminal tab for running a process
   */
  public async createProcessTab(options: ICreateTerminalTabOptions): Promise<string> {
    const tab = this.tabManager.createTab(options, this.isBright);

    this.tabs = this.tabManager.getAllTabs();

    // Switch to new tab if requested (default: true)
    if (options.switchToTab !== false) {
      this.switchToTab(tab.id);
    }

    // Wait for DOM update
    await this.updateComplete;

    // Spawn process if command provided
    if (options.command) {
      await this.spawnProcessForTab(tab, options.command, options.args);
    }

    // Dispatch event
    this.dispatchEvent(
      new CustomEvent('tab-created', {
        bubbles: true,
        composed: true,
        detail: { tabId: tab.id },
      })
    );

    return tab.id;
  }

  /**
   * Get the currently active tab
   */
  public getActiveTab(): ITerminalTab | null {
    if (!this.activeTabId) return null;
    return this.tabManager.getTab(this.activeTabId) || null;
  }

  /**
   * Get all tabs
   */
  public getTabs(): ITerminalTab[] {
    return this.tabManager.getAllTabs();
  }

  /**
   * Switch to a specific tab by ID
   */
  public selectTab(tabId: string): boolean {
    if (!this.tabManager.hasTab(tabId)) return false;
    this.switchToTab(tabId);
    return true;
  }

  /**
   * Close a tab by ID
   */
  public closeTab(tabId: string): boolean {
    const tab = this.tabManager.getTab(tabId);
    if (!tab || !tab.closeable) return false;

    // If closing active tab, switch to another
    if (tabId === this.activeTabId) {
      const allTabs = this.tabManager.getAllTabs();
      const currentIndex = allTabs.findIndex((t) => t.id === tabId);
      const nextTab = allTabs[currentIndex + 1] || allTabs[currentIndex - 1];

      if (nextTab) {
        this.switchToTab(nextTab.id);
      } else {
        this.activeTabId = null;
        this.detachActiveTerminal();
      }
    }

    // Close the tab
    this.tabManager.closeTab(tabId);
    this.tabs = this.tabManager.getAllTabs();

    // Dispatch event
    this.dispatchEvent(
      new CustomEvent('tab-closed', {
        bubbles: true,
        composed: true,
        detail: { tabId },
      })
    );

    return true;
  }

  /**
   * Write data to a tab's terminal
   */
  public writeToTab(tabId: string, data: string): boolean {
    const tab = this.tabManager.getTab(tabId);
    if (!tab) return false;
    tab.terminal.write(data);
    return true;
  }

  /**
   * Send input to a tab's process (if interactive)
   */
  public sendInputToTab(tabId: string, data: string): boolean {
    const tab = this.tabManager.getTab(tabId);
    if (!tab || !tab.inputWriter) return false;
    tab.inputWriter.write(data);
    return true;
  }

  /**
   * Show an actionbar notification in the terminal panel context.
   * Use this for terminal-related decisions (e.g., retry failed process, kill process, etc.)
   */
  public async showActionbar(
    options: Parameters<DeesActionbar['show']>[0]
  ): Promise<ReturnType<DeesActionbar['show']>> {
    if (!this.terminalActionbar) {
      this.terminalActionbar = this.shadowRoot?.querySelector('dees-actionbar') as DeesActionbar;
    }
    return this.terminalActionbar?.show(options);
  }

  // ========== Utility Methods ==========

  public async waitForPrompt(term: Terminal, prompt: string): Promise<void> {
    return new Promise<void>((resolve) => {
      const checkPrompt = () => {
        const lines = term.buffer.active;
        for (let i = 0; i < lines.length; i++) {
          const line = lines.getLine(i);
          if (line && line.translateToString().includes(prompt)) {
            setTimeout(() => {
              resolve();
            }, 100);
            return;
          }
        }
        setTimeout(checkPrompt, 100);
      };

      checkPrompt();
    });
  }

  public async setEnvironmentVariables(envArg: { [key: string]: string }): Promise<void> {
    if (!this.executionEnvironment) {
      throw new Error('No execution environment available');
    }

    let envFile = '';
    for (const key in envArg) {
      envFile += `export ${key}="${envArg[key]}"\n`;
    }

    await this.executionEnvironment.writeFile('/source.env', envFile);
  }

  /**
   * Get the underlying execution environment.
   */
  public getExecutionEnvironment(): IExecutionEnvironment | null {
    return this.executionEnvironment;
  }
}
