import {
  DeesElement,
  property,
  html,
  customElement,
  type TemplateResult,
  css,
  cssManager,
  state,
} from '@design.estate/dees-element';
import { themeDefaultStyles } from '../../00theme.js';
import type { IExecutionEnvironment } from '../../00group-runtime/index.js';
import '../../dees-icon/dees-icon.js';
import { DeesContextmenu } from '../../dees-contextmenu/dees-contextmenu.js';
import type { IRunProcessEventDetail, ITerminalProcessCompleteEventDetail } from '../dees-workspace-terminal/interfaces.js';

declare global {
  interface HTMLElementTagNameMap {
    'dees-workspace-bottombar': DeesWorkspaceBottombar;
  }
}

interface IOutdatedPackage {
  name: string;
  current: string;
  wanted: string;
  latest: string;
  type: 'dependencies' | 'devDependencies';
}

@customElement('dees-workspace-bottombar')
export class DeesWorkspaceBottombar extends DeesElement {
  // INSTANCE
  @property({ type: Object })
  accessor executionEnvironment: IExecutionEnvironment | null = null;

  // Script runner state
  @state()
  accessor scripts: Record<string, string> = {};

  // Package checker state
  @state()
  accessor packageStatus: 'checking' | 'up-to-date' | 'updates-available' | 'error' | 'idle' = 'idle';

  @state()
  accessor outdatedPackages: IOutdatedPackage[] = [];

  @state()
  accessor isCheckingPackages: boolean = false;

  // Track if we have a pending package update that should trigger refresh
  private pendingPackageUpdate: boolean = false;

  // Bound handler for process-complete events
  private handleProcessComplete = (e: CustomEvent<ITerminalProcessCompleteEventDetail>) => {
    // If we have a pending package update and a process completed, refresh
    if (this.pendingPackageUpdate) {
      this.pendingPackageUpdate = false;
      // Small delay to let pnpm-lock.yaml update
      setTimeout(() => this.checkPackages(), 500);
    }
  };

  public static styles = [
    themeDefaultStyles,
    cssManager.defaultStyles,
    css`
      :host {
        display: block;
        height: 24px;
        flex-shrink: 0;
      }

      .bottom-bar {
        height: 24px;
        display: flex;
        align-items: center;
        padding: 0 8px;
        gap: 4px;
        background: ${cssManager.bdTheme('hsl(0 0% 94%)', 'hsl(0 0% 6%)')};
        border-top: 1px solid ${cssManager.bdTheme('hsl(0 0% 85%)', 'hsl(0 0% 15%)')};
        font-size: 11px;
        color: ${cssManager.bdTheme('hsl(0 0% 40%)', 'hsl(0 0% 60%)')};
      }

      .widget {
        display: flex;
        align-items: center;
        gap: 4px;
        padding: 2px 6px;
        border-radius: 3px;
        cursor: pointer;
        transition: background 0.15s ease, color 0.15s ease;
        white-space: nowrap;
      }

      .widget:hover {
        background: ${cssManager.bdTheme('hsl(0 0% 88%)', 'hsl(0 0% 12%)')};
        color: ${cssManager.bdTheme('hsl(0 0% 20%)', 'hsl(0 0% 80%)')};
      }

      .widget dees-icon {
        flex-shrink: 0;
      }

      .widget-separator {
        width: 1px;
        height: 14px;
        background: ${cssManager.bdTheme('hsl(0 0% 80%)', 'hsl(0 0% 20%)')};
        margin: 0 4px;
      }

      .widget.running {
        color: ${cssManager.bdTheme('hsl(210 100% 45%)', 'hsl(210 100% 60%)')};
      }

      .widget.up-to-date {
        color: ${cssManager.bdTheme('hsl(142 70% 35%)', 'hsl(142 70% 50%)')};
      }

      .widget.updates-available {
        color: ${cssManager.bdTheme('hsl(38 92% 45%)', 'hsl(38 92% 55%)')};
      }

      .widget.error {
        color: ${cssManager.bdTheme('hsl(0 70% 50%)', 'hsl(0 70% 60%)')};
      }

      @keyframes spin {
        from { transform: rotate(0deg); }
        to { transform: rotate(360deg); }
      }

      .spinning {
        animation: spin 1s linear infinite;
      }

      .spacer {
        flex: 1;
      }
    `,
  ];

  public render(): TemplateResult {
    return html`
      <div class="bottom-bar">
        <!-- Script Runner Widget -->
        <div
          class="widget"
          @click=${this.handleScriptClick}
          title="Run script"
        >
          <dees-icon
            .icon=${'lucide:play'}
            iconSize="12"
          ></dees-icon>
          <span>Scripts</span>
        </div>

        <div class="widget-separator"></div>

        <!-- Package Checker Widget -->
        <div
          class="widget ${this.getPackageStatusClass()}"
          @click=${this.handlePackageClick}
          title="${this.getPackageTooltip()}"
        >
          <dees-icon
            .icon=${this.getPackageIcon()}
            iconSize="12"
            class="${this.isCheckingPackages ? 'spinning' : ''}"
          ></dees-icon>
          <span>${this.getPackageStatusText()}</span>
        </div>

        <div class="spacer"></div>

        <!-- Future widgets can be added here -->
      </div>
    `;
  }

  public async connectedCallback() {
    await super.connectedCallback();
    // Listen for process-complete events to refresh after package updates
    window.addEventListener('process-complete', this.handleProcessComplete as EventListener);
  }

  public async disconnectedCallback() {
    await super.disconnectedCallback();
    window.removeEventListener('process-complete', this.handleProcessComplete as EventListener);
  }

  async firstUpdated() {
    await this.loadScripts();
    await this.checkPackages();
  }

  async updated(changedProperties: Map<string, any>) {
    if (changedProperties.has('executionEnvironment') && this.executionEnvironment) {
      await this.loadScripts();
      await this.checkPackages();
    }
  }

  // ========== Script Runner ==========

  private async loadScripts(): Promise<void> {
    if (!this.executionEnvironment) return;

    try {
      const packageJsonExists = await this.executionEnvironment.exists('/package.json');
      if (!packageJsonExists) {
        this.scripts = {};
        return;
      }

      const content = await this.executionEnvironment.readFile('/package.json');
      const packageJson = JSON.parse(content);
      this.scripts = packageJson.scripts || {};
    } catch (error) {
      console.warn('Failed to load scripts from package.json:', error);
      this.scripts = {};
    }
  }

  private async handleScriptClick(e: MouseEvent): Promise<void> {
    e.stopPropagation();

    const scriptNames = Object.keys(this.scripts);
    if (scriptNames.length === 0) {
      return;
    }

    const menuItems = scriptNames.map(name => ({
      name: name,
      iconName: 'lucide:terminal' as const,
      action: async () => {
        await this.runScript(name);
      },
    }));

    await DeesContextmenu.openContextMenuWithOptions(e, menuItems);
  }

  private async runScript(scriptName: string): Promise<void> {
    if (!this.executionEnvironment) return;

    // Emit run-process event for the workspace to create a terminal tab
    const detail: IRunProcessEventDetail = {
      type: 'script',
      label: scriptName,
      command: 'pnpm',
      args: ['run', scriptName],
      metadata: { scriptName },
    };

    this.dispatchEvent(new CustomEvent('run-process', {
      bubbles: true,
      composed: true,
      detail,
    }));
  }

  // ========== Package Checker ==========

  private async checkPackages(): Promise<void> {
    if (!this.executionEnvironment) {
      this.packageStatus = 'idle';
      return;
    }

    try {
      const packageJsonExists = await this.executionEnvironment.exists('/package.json');
      if (!packageJsonExists) {
        this.packageStatus = 'idle';
        return;
      }

      this.packageStatus = 'checking';
      this.isCheckingPackages = true;

      // Run pnpm outdated --json with timeout
      const process = await this.executionEnvironment.spawn('pnpm', ['outdated', '--json']);

      let output = '';

      // Collect output asynchronously - don't await, stream may not close if no output
      const outputReader = process.output.getReader();
      const readOutput = async () => {
        try {
          while (true) {
            const { done, value } = await outputReader.read();
            if (done) break;
            output += value;
          }
        } catch {
          // Ignore stream errors
        }
      };
      // Start reading but don't await - we'll use whatever we have when process exits
      readOutput();

      // Wait for process exit with timeout (10 seconds)
      const exitCode = await Promise.race([
        process.exit,
        new Promise<number>((resolve) => setTimeout(() => resolve(-1), 10000)),
      ]);

      // Cancel reader when done
      try {
        await outputReader.cancel();
      } catch {
        // Ignore cancel errors
      }

      // Handle timeout
      if (exitCode === -1) {
        console.warn('Package check timed out');
        this.packageStatus = 'error';
        return;
      }

      // pnpm outdated returns exit code 1 if there are outdated packages
      if (exitCode === 0) {
        // No outdated packages
        this.packageStatus = 'up-to-date';
        this.outdatedPackages = [];
      } else {
        // Parse outdated packages
        try {
          const outdatedData = JSON.parse(output);
          this.outdatedPackages = this.parseOutdatedPackages(outdatedData);
          this.packageStatus = this.outdatedPackages.length > 0 ? 'updates-available' : 'up-to-date';
        } catch {
          // If parsing fails but exit code is 1, assume there are updates
          this.packageStatus = 'updates-available';
          this.outdatedPackages = [];
        }
      }
    } catch (error) {
      console.warn('Failed to check for package updates:', error);
      this.packageStatus = 'error';
    } finally {
      this.isCheckingPackages = false;
    }
  }

  private parseOutdatedPackages(data: any): IOutdatedPackage[] {
    const packages: IOutdatedPackage[] = [];

    // pnpm outdated --json returns an object with package names as keys
    if (typeof data === 'object' && data !== null) {
      for (const [name, info] of Object.entries(data)) {
        const pkgInfo = info as any;
        packages.push({
          name,
          current: pkgInfo.current || 'unknown',
          wanted: pkgInfo.wanted || pkgInfo.current || 'unknown',
          latest: pkgInfo.latest || pkgInfo.wanted || 'unknown',
          type: pkgInfo.dependencyType === 'devDependencies' ? 'devDependencies' : 'dependencies',
        });
      }
    }

    return packages;
  }

  private async handlePackageClick(e: MouseEvent): Promise<void> {
    e.stopPropagation();

    const menuItems: Parameters<typeof DeesContextmenu.openContextMenuWithOptions>[1] = [];

    // Refresh option - show output in terminal
    menuItems.push({
      name: this.isCheckingPackages ? 'Checking...' : 'Check for updates',
      iconName: 'lucide:refreshCw',
      action: async () => {
        if (this.isCheckingPackages) return;

        // Create terminal tab to show pnpm outdated output
        const detail: IRunProcessEventDetail = {
          type: 'package-update',
          label: 'check packages',
          command: 'pnpm',
          args: ['outdated'],
        };

        this.dispatchEvent(new CustomEvent('run-process', {
          bubbles: true,
          composed: true,
          detail,
        }));

        // Also refresh the widget status silently after a delay
        setTimeout(() => this.checkPackages(), 3000);
      },
    });

    if (this.outdatedPackages.length > 0) {
      menuItems.push({ divider: true });

      // Show outdated packages (max 10)
      const displayPackages = this.outdatedPackages.slice(0, 10);
      for (const pkg of displayPackages) {
        menuItems.push({
          name: `${pkg.name}: ${pkg.current} → ${pkg.latest}`,
          iconName: 'lucide:package',
          action: async () => {
            // Update single package
            await this.updatePackage(pkg.name);
          },
        });
      }

      if (this.outdatedPackages.length > 10) {
        menuItems.push({
          name: `... and ${this.outdatedPackages.length - 10} more`,
          iconName: 'lucide:moreHorizontal',
          action: async () => {},
        });
      }

      menuItems.push({ divider: true });

      menuItems.push({
        name: 'Update all packages',
        iconName: 'lucide:arrowUpCircle',
        action: async () => {
          await this.updateAllPackages();
        },
      });
    }

    await DeesContextmenu.openContextMenuWithOptions(e, menuItems);
  }

  private async updatePackage(packageName: string): Promise<void> {
    if (!this.executionEnvironment) return;

    // Mark that we have a pending update - will trigger refresh when complete
    this.pendingPackageUpdate = true;

    // Emit run-process event for the workspace to create a terminal tab
    const detail: IRunProcessEventDetail = {
      type: 'package-update',
      label: `update ${packageName}`,
      command: 'pnpm',
      args: ['update', '--latest', packageName],
      metadata: { packageName },
    };

    this.dispatchEvent(new CustomEvent('run-process', {
      bubbles: true,
      composed: true,
      detail,
    }));
  }

  private async updateAllPackages(): Promise<void> {
    if (!this.executionEnvironment) return;

    // Mark that we have a pending update - will trigger refresh when complete
    this.pendingPackageUpdate = true;

    // Emit run-process event for the workspace to create a terminal tab
    const detail: IRunProcessEventDetail = {
      type: 'package-update',
      label: 'update all',
      command: 'pnpm',
      args: ['update', '--latest'],
    };

    this.dispatchEvent(new CustomEvent('run-process', {
      bubbles: true,
      composed: true,
      detail,
    }));
  }

  // ========== Helper Methods ==========

  private getPackageStatusClass(): string {
    if (this.isCheckingPackages) return '';
    return this.packageStatus;
  }

  private getPackageIcon(): string {
    if (this.isCheckingPackages) return 'lucide:loader2';

    switch (this.packageStatus) {
      case 'up-to-date':
        return 'lucide:checkCircle';
      case 'updates-available':
        return 'lucide:alertCircle';
      case 'error':
        return 'lucide:xCircle';
      default:
        return 'lucide:package';
    }
  }

  private getPackageStatusText(): string {
    if (this.isCheckingPackages) return 'Checking...';

    switch (this.packageStatus) {
      case 'up-to-date':
        return 'Up to date';
      case 'updates-available':
        return `${this.outdatedPackages.length} update${this.outdatedPackages.length !== 1 ? 's' : ''}`;
      case 'error':
        return 'Check failed';
      default:
        return 'Packages';
    }
  }

  private getPackageTooltip(): string {
    if (this.isCheckingPackages) return 'Checking for package updates...';

    switch (this.packageStatus) {
      case 'up-to-date':
        return 'All packages are up to date';
      case 'updates-available':
        return `${this.outdatedPackages.length} package update${this.outdatedPackages.length !== 1 ? 's' : ''} available`;
      case 'error':
        return 'Failed to check for updates. Click to retry.';
      default:
        return 'Click to check for package updates';
    }
  }

  // ========== Public Methods ==========

  /**
   * Manually trigger a package check
   */
  public async refreshPackageStatus(): Promise<void> {
    await this.checkPackages();
  }

  /**
   * Manually reload scripts from package.json
   */
  public async refreshScripts(): Promise<void> {
    await this.loadScripts();
  }
}
