import * as plugins from '../plugins.js';
import { apiService, type IS3Object } from '../services/index.js';
import { getFileName, validateMove, getParentPrefix } from '../utilities/index.js';
import { themeStyles } from '../styles/index.js';

const { html, css, cssManager, customElement, property, state, DeesElement } = plugins;
const { DeesContextmenu } = plugins.deesCatalog;

// FileSystem API types for drag-and-drop folder support
interface FileSystemEntry {
  isFile: boolean;
  isDirectory: boolean;
  name: string;
}
interface FileSystemFileEntry extends FileSystemEntry {
  file(successCallback: (file: File) => void, errorCallback?: (error: Error) => void): void;
}
interface FileSystemDirectoryEntry extends FileSystemEntry {
  createReader(): FileSystemDirectoryReader;
}
interface FileSystemDirectoryReader {
  readEntries(
    successCallback: (entries: FileSystemEntry[]) => void,
    errorCallback?: (error: Error) => void
  ): void;
}

interface IColumn {
  prefix: string;
  objects: IS3Object[];
  prefixes: string[];
  selectedItem: string | null;
  width: number;
}

@customElement('tsview-s3-columns')
export class TsviewS3Columns extends DeesElement {
  @property({ type: String })
  public accessor bucketName: string = '';

  @property({ type: String })
  public accessor currentPrefix: string = '';

  @property({ type: Number })
  public accessor refreshKey: number = 0;

  @state()
  private accessor columns: IColumn[] = [];

  @state()
  private accessor loading: boolean = false;

  @state()
  private accessor showCreateDialog: boolean = false;

  @state()
  private accessor createDialogType: 'folder' | 'file' = 'folder';

  @state()
  private accessor createDialogPrefix: string = '';

  @state()
  private accessor createDialogName: string = '';

  @state()
  private accessor dragOverColumnIndex: number = -1;

  @state()
  private accessor dragOverFolderPrefix: string | null = null;

  @state()
  private accessor uploading: boolean = false;

  @state()
  private accessor uploadProgress: { current: number; total: number } | null = null;

  // Move dialog state
  @state()
  private accessor showMoveDialog: boolean = false;

  @state()
  private accessor moveSource: { key: string; isFolder: boolean } | null = null;

  @state()
  private accessor moveDestination: string = '';

  @state()
  private accessor moveInProgress: boolean = false;

  @state()
  private accessor moveError: string | null = null;

  // Move picker dialog state
  @state()
  private accessor showMovePickerDialog: boolean = false;

  @state()
  private accessor movePickerSource: { key: string; isFolder: boolean } | null = null;

  @state()
  private accessor movePickerCurrentPrefix: string = '';

  @state()
  private accessor movePickerPrefixes: string[] = [];

  @state()
  private accessor movePickerLoading: boolean = false;

  // Rename dialog state
  @state()
  private accessor showRenameDialog: boolean = false;

  @state()
  private accessor renameSource: { key: string; isFolder: boolean } | null = null;

  @state()
  private accessor renameName: string = '';

  @state()
  private accessor renameInProgress: boolean = false;

  @state()
  private accessor renameError: string | null = null;

  // Internal drag state
  @state()
  private accessor draggedItem: { key: string; isFolder: boolean } | null = null;

  private resizing: { columnIndex: number; startX: number; startWidth: number } | null = null;
  private readonly DEFAULT_COLUMN_WIDTH = 250;
  private readonly MIN_COLUMN_WIDTH = 150;
  private readonly MAX_COLUMN_WIDTH = 500;
  private dragCounters: Map<number, number> = new Map();
  private folderHoverTimer: ReturnType<typeof setTimeout> | null = null;
  private fileInputElement: HTMLInputElement | null = null;
  private folderInputElement: HTMLInputElement | null = null;

  public static styles = [
    cssManager.defaultStyles,
    themeStyles,
    css`
      :host {
        display: block;
        height: 100%;
        overflow-x: auto;
        overflow-y: hidden;
      }

      .columns-container {
        display: flex;
        height: 100%;
        min-width: max-content;
      }

      .column-wrapper {
        display: flex;
        height: 100%;
        flex-shrink: 0;
      }

      .column {
        display: flex;
        flex-direction: column;
        height: 100%;
        flex-shrink: 0;
        overflow: hidden;
        position: relative;
      }

      .resize-handle {
        width: 5px;
        height: 100%;
        background: transparent;
        cursor: col-resize;
        position: relative;
        flex-shrink: 0;
      }

      .resize-handle::after {
        content: '';
        position: absolute;
        top: 0;
        left: 2px;
        width: 1px;
        height: 100%;
        background: #333;
      }

      .resize-handle:hover::after,
      .resize-handle.active::after {
        background: #404040;
        width: 2px;
        left: 1px;
      }

      .column-wrapper:last-child .resize-handle {
        display: none;
      }

      .column-header {
        padding: 8px 12px;
        font-size: 12px;
        font-weight: 500;
        color: #666;
        background: rgba(0, 0, 0, 0.2);
        border-bottom: 1px solid #333;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
      }

      .column-items {
        flex: 1;
        overflow-y: auto;
        padding: 4px;
      }

      .column-item {
        display: flex;
        align-items: center;
        gap: 8px;
        padding: 6px 10px;
        border-radius: 4px;
        cursor: pointer;
        font-size: 13px;
        transition: background 0.1s;
      }

      .column-item:hover {
        background: rgba(255, 255, 255, 0.05);
      }

      .column-item.selected {
        background: rgba(255, 255, 255, 0.1);
        color: #e0e0e0;
      }

      .column-item.folder {
        color: #fbbf24;
      }

      .column-item .icon {
        width: 16px;
        height: 16px;
        flex-shrink: 0;
      }

      .column-item .name {
        flex: 1;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
      }

      .column-item .chevron {
        width: 14px;
        height: 14px;
        color: #555;
      }

      .empty-state {
        padding: 16px;
        text-align: center;
        color: #666;
        font-size: 13px;
      }

      .loading {
        padding: 16px;
        text-align: center;
        color: #666;
      }

      .column.drag-over {
        background: rgba(59, 130, 246, 0.08);
        outline: 2px dashed rgba(59, 130, 246, 0.4);
        outline-offset: -2px;
      }

      .column-item.folder.drag-target {
        background: rgba(59, 130, 246, 0.2) !important;
        outline: 1px solid rgba(59, 130, 246, 0.5);
      }

      .upload-overlay {
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        background: rgba(0, 0, 0, 0.6);
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        gap: 8px;
        z-index: 10;
        border-radius: 4px;
      }

      .upload-overlay .upload-text {
        color: #e0e0e0;
        font-size: 13px;
      }

      .upload-overlay .upload-progress {
        color: #888;
        font-size: 11px;
      }

      .drag-hint {
        position: absolute;
        bottom: 8px;
        left: 50%;
        transform: translateX(-50%);
        background: rgba(59, 130, 246, 0.9);
        color: white;
        padding: 4px 12px;
        border-radius: 4px;
        font-size: 11px;
        pointer-events: none;
        white-space: nowrap;
        z-index: 5;
      }

      .dialog-overlay {
        position: fixed;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        background: rgba(0, 0, 0, 0.7);
        display: flex;
        align-items: center;
        justify-content: center;
        z-index: 1000;
      }

      .dialog {
        background: #1e1e1e;
        border-radius: 12px;
        padding: 24px;
        min-width: 400px;
        box-shadow: 0 20px 40px rgba(0, 0, 0, 0.5);
      }

      .dialog-title {
        font-size: 18px;
        font-weight: 600;
        margin-bottom: 16px;
        color: #fff;
      }

      .dialog-location {
        font-size: 12px;
        color: #888;
        margin-bottom: 12px;
        font-family: monospace;
      }

      .dialog-input {
        width: 100%;
        padding: 10px 12px;
        background: #141414;
        border: 1px solid #333;
        border-radius: 6px;
        color: #fff;
        font-size: 14px;
        margin-bottom: 8px;
        box-sizing: border-box;
      }

      .dialog-input:focus {
        outline: none;
        border-color: #e0e0e0;
      }

      .dialog-hint {
        font-size: 11px;
        color: #666;
        margin-bottom: 16px;
      }

      .dialog-actions {
        display: flex;
        gap: 12px;
        justify-content: flex-end;
      }

      .dialog-btn {
        padding: 8px 16px;
        border-radius: 6px;
        font-size: 14px;
        cursor: pointer;
        transition: all 0.2s;
      }

      .dialog-btn-cancel {
        background: transparent;
        border: 1px solid #444;
        color: #aaa;
      }

      .dialog-btn-cancel:hover {
        background: rgba(255, 255, 255, 0.05);
        color: #fff;
      }

      .dialog-btn-create {
        background: #404040;
        border: none;
        color: #fff;
      }

      .dialog-btn-create:hover {
        background: #505050;
      }

      .dialog-btn-create:disabled {
        opacity: 0.5;
        cursor: not-allowed;
      }

      /* Move dialog styles */
      .move-summary {
        background: rgba(0, 0, 0, 0.2);
        border-radius: 8px;
        padding: 16px;
        margin-bottom: 16px;
      }

      .move-item {
        display: flex;
        gap: 8px;
        margin-bottom: 8px;
      }

      .move-item:last-child {
        margin-bottom: 0;
      }

      .move-label {
        color: #888;
        min-width: 40px;
      }

      .move-path {
        color: #e0e0e0;
        font-family: monospace;
        font-size: 12px;
        word-break: break-all;
      }

      .move-error {
        background: rgba(239, 68, 68, 0.15);
        border: 1px solid rgba(239, 68, 68, 0.3);
        color: #f87171;
        padding: 12px 16px;
        border-radius: 8px;
        margin-bottom: 16px;
        font-size: 13px;
      }

      /* Move picker dialog styles */
      .move-picker-dialog {
        min-width: 450px;
        max-height: 500px;
      }

      .picker-breadcrumb {
        display: flex;
        align-items: center;
        gap: 4px;
        padding: 8px 12px;
        background: rgba(0, 0, 0, 0.2);
        border-radius: 6px;
        margin-bottom: 12px;
        font-size: 12px;
        overflow-x: auto;
      }

      .picker-crumb {
        color: #888;
        cursor: pointer;
        white-space: nowrap;
      }

      .picker-crumb:hover {
        color: #fff;
      }

      .picker-separator {
        color: #555;
      }

      .picker-list {
        max-height: 280px;
        overflow-y: auto;
        border: 1px solid #333;
        border-radius: 6px;
        margin-bottom: 16px;
        min-height: 100px;
      }

      .picker-item {
        display: flex;
        align-items: center;
        gap: 8px;
        padding: 8px 12px;
        cursor: pointer;
        color: #fbbf24;
      }

      .picker-item:hover {
        background: rgba(255, 255, 255, 0.05);
      }

      .picker-item .icon {
        width: 16px;
        height: 16px;
        flex-shrink: 0;
      }

      .picker-item .name {
        flex: 1;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
      }

      .picker-empty {
        padding: 24px;
        text-align: center;
        color: #666;
        font-size: 13px;
      }

      .column-item.dragging {
        opacity: 0.5;
      }

      .column-item.drop-target {
        background: rgba(59, 130, 246, 0.15) !important;
        outline: 1px dashed rgba(59, 130, 246, 0.5);
      }

      .column-item.drop-invalid {
        background: rgba(239, 68, 68, 0.1) !important;
        cursor: not-allowed;
      }
    `,
  ];

  async connectedCallback() {
    super.connectedCallback();
    await this.loadInitialColumn();
  }

  disconnectedCallback() {
    super.disconnectedCallback();
    this.clearFolderHover();
    this.dragCounters.clear();
    if (this.fileInputElement) { this.fileInputElement.remove(); this.fileInputElement = null; }
    if (this.folderInputElement) { this.folderInputElement.remove(); this.folderInputElement = null; }
  }

  updated(changedProperties: Map<string, unknown>) {
    if (changedProperties.has('bucketName') || changedProperties.has('currentPrefix')) {
      this.loadInitialColumn();
    } else if (changedProperties.has('refreshKey')) {
      this.refreshAllColumns();
    }
  }

  private async loadInitialColumn() {
    this.loading = true;
    try {
      // Parse the path segments from currentPrefix
      // e.g., "folder1/folder2/folder3/" → ["folder1/", "folder1/folder2/", "folder1/folder2/folder3/"]
      const pathSegments = this.getPathSegments(this.currentPrefix);

      // Build all prefixes we need to load (including root)
      const prefixesToLoad = ['', ...pathSegments];

      // Load all columns in parallel
      const columnResults = await Promise.all(
        prefixesToLoad.map(prefix =>
          apiService.listObjects(this.bucketName, prefix, '/')
        )
      );

      // Build columns array with proper selections
      this.columns = columnResults.map((result, index) => {
        const prefix = prefixesToLoad[index];
        // The selected item is the next prefix in the path (if any)
        const selectedItem = index < pathSegments.length ? pathSegments[index] : null;

        return {
          prefix,
          objects: result.objects,
          prefixes: result.prefixes,
          selectedItem,
          width: this.DEFAULT_COLUMN_WIDTH,
        };
      });

      // Auto-scroll to show the rightmost column
      this.updateComplete.then(() => this.scrollToEnd());
    } catch (err) {
      console.error('Error loading objects:', err);
      this.columns = [];
    }
    this.loading = false;
  }

  // Helper to parse prefix into cumulative path segments
  private getPathSegments(prefix: string): string[] {
    if (!prefix) return [];

    const parts = prefix.split('/').filter(p => p); // Remove empty strings
    const segments: string[] = [];
    let cumulative = '';

    for (const part of parts) {
      cumulative += part + '/';
      segments.push(cumulative);
    }

    return segments;
  }

  private async refreshAllColumns() {
    const updatedColumns = await Promise.all(
      this.columns.map(async (col) => {
        try {
          const result = await apiService.listObjects(this.bucketName, col.prefix, '/');
          return { ...col, objects: result.objects, prefixes: result.prefixes };
        } catch {
          return col;
        }
      })
    );
    this.columns = updatedColumns;
  }

  private async selectFolder(columnIndex: number, prefix: string) {
    // Update selection in current column
    this.columns = this.columns.map((col, i) => {
      if (i === columnIndex) {
        return { ...col, selectedItem: prefix };
      }
      return col;
    });

    // Remove columns after current
    this.columns = this.columns.slice(0, columnIndex + 1);

    // Load new column
    try {
      const result = await apiService.listObjects(this.bucketName, prefix, '/');
      this.columns = [
        ...this.columns,
        {
          prefix,
          objects: result.objects,
          prefixes: result.prefixes,
          selectedItem: null,
          width: this.DEFAULT_COLUMN_WIDTH,
        },
      ];
      // Auto-scroll to show the new column
      this.updateComplete.then(() => this.scrollToEnd());
    } catch (err) {
      console.error('Error loading folder:', err);
    }

    // Note: Don't dispatch navigate event here - columns view expands horizontally
    // The navigate event is only for breadcrumb sync, not for column navigation
  }

  private scrollToEnd() {
    this.scrollLeft = this.scrollWidth - this.clientWidth;
  }

  private startResize(e: MouseEvent, columnIndex: number) {
    e.preventDefault();
    this.resizing = {
      columnIndex,
      startX: e.clientX,
      startWidth: this.columns[columnIndex].width,
    };
    document.addEventListener('mousemove', this.handleResize);
    document.addEventListener('mouseup', this.stopResize);
    document.body.style.cursor = 'col-resize';
    document.body.style.userSelect = 'none';
  }

  private handleResize = (e: MouseEvent) => {
    if (!this.resizing) return;

    const delta = e.clientX - this.resizing.startX;
    const newWidth = Math.min(
      this.MAX_COLUMN_WIDTH,
      Math.max(this.MIN_COLUMN_WIDTH, this.resizing.startWidth + delta)
    );

    this.columns = this.columns.map((col, i) => {
      if (i === this.resizing!.columnIndex) {
        return { ...col, width: newWidth };
      }
      return col;
    });
  };

  private stopResize = () => {
    this.resizing = null;
    document.removeEventListener('mousemove', this.handleResize);
    document.removeEventListener('mouseup', this.stopResize);
    document.body.style.cursor = '';
    document.body.style.userSelect = '';
  };

  private selectFile(columnIndex: number, key: string) {
    // Update selection
    this.columns = this.columns.map((col, i) => {
      if (i === columnIndex) {
        return { ...col, selectedItem: key };
      }
      return col;
    });

    // Remove columns after current
    this.columns = this.columns.slice(0, columnIndex + 1);

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

  private getFileIcon(key: string): string {
    const ext = key.split('.').pop()?.toLowerCase() || '';
    const iconMap: Record<string, string> = {
      json: 'M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z',
      txt: 'M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z',
      png: 'M5 3h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z',
      jpg: 'M5 3h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z',
      jpeg: 'M5 3h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z',
      gif: 'M5 3h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z',
      pdf: 'M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z',
    };
    return iconMap[ext] || 'M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z';
  }

  private handleFolderContextMenu(event: MouseEvent, columnIndex: number, prefix: string) {
    event.preventDefault();
    DeesContextmenu.openContextMenuWithOptions(event, [
      {
        name: 'Open',
        iconName: 'lucide:folderOpen',
        action: async () => {
          this.selectFolder(columnIndex, prefix);
        },
      },
      {
        name: 'Copy Path',
        iconName: 'lucide:copy',
        action: async () => {
          await navigator.clipboard.writeText(prefix);
        },
      },
      {
        name: 'Rename',
        iconName: 'lucide:pencil',
        action: async () => this.openRenameDialog(prefix, true),
      },
      { divider: true },
      {
        name: 'Move to...',
        iconName: 'lucide:folderInput',
        action: async () => this.openMovePickerDialog(prefix, true),
      },
      { divider: true },
      {
        name: 'New Folder Inside',
        iconName: 'lucide:folderPlus',
        action: async () => this.openCreateDialog('folder', prefix),
      },
      {
        name: 'New File Inside',
        iconName: 'lucide:filePlus',
        action: async () => this.openCreateDialog('file', prefix),
      },
      {
        name: 'Upload Files...',
        iconName: 'lucide:file',
        action: async () => this.triggerFileUpload(prefix, 'files'),
      },
      {
        name: 'Upload Folder...',
        iconName: 'lucide:folderUp',
        action: async () => this.triggerFileUpload(prefix, 'folder'),
      },
      { divider: true },
      {
        name: 'Delete Folder',
        iconName: 'lucide:trash2',
        action: async () => {
          if (confirm(`Delete folder "${getFileName(prefix)}" and all its contents?`)) {
            const success = await apiService.deletePrefix(this.bucketName, prefix);
            if (success) {
              // Remove columns that were inside the deleted folder
              this.columns = this.columns.slice(0, columnIndex + 1);
              this.columns = this.columns.map((col, i) =>
                i === columnIndex ? { ...col, selectedItem: null } : col
              );
              await this.refreshColumnByPrefix(this.columns[columnIndex].prefix);
            }
          }
        },
      },
    ]);
  }

  private handleFileContextMenu(event: MouseEvent, columnIndex: number, key: string) {
    event.preventDefault();
    DeesContextmenu.openContextMenuWithOptions(event, [
      {
        name: 'Preview',
        iconName: 'lucide:eye',
        action: async () => {
          this.selectFile(columnIndex, key);
        },
      },
      {
        name: 'Download',
        iconName: 'lucide:download',
        action: async () => {
          const url = await apiService.getObjectUrl(this.bucketName, key);
          const link = document.createElement('a');
          link.href = url;
          link.download = getFileName(key);
          link.click();
        },
      },
      {
        name: 'Copy Path',
        iconName: 'lucide:copy',
        action: async () => {
          await navigator.clipboard.writeText(key);
        },
      },
      {
        name: 'Rename',
        iconName: 'lucide:pencil',
        action: async () => this.openRenameDialog(key, false),
      },
      { divider: true },
      {
        name: 'Move to...',
        iconName: 'lucide:folderInput',
        action: async () => this.openMovePickerDialog(key, false),
      },
      { divider: true },
      {
        name: 'Delete',
        iconName: 'lucide:trash2',
        action: async () => {
          if (confirm(`Delete file "${getFileName(key)}"?`)) {
            const success = await apiService.deleteObject(this.bucketName, key);
            if (success) {
              await this.refreshColumnByPrefix(this.columns[columnIndex].prefix);
            }
          }
        },
      },
    ]);
  }

  private handleEmptySpaceContextMenu(event: MouseEvent, columnIndex: number) {
    // Only trigger if clicking on the container itself, not on items
    if (event.target !== event.currentTarget) return;
    event.preventDefault();

    const prefix = this.columns[columnIndex].prefix;

    DeesContextmenu.openContextMenuWithOptions(event, [
      {
        name: 'New Folder',
        iconName: 'lucide:folderPlus',
        action: async () => this.openCreateDialog('folder', prefix),
      },
      {
        name: 'New File',
        iconName: 'lucide:filePlus',
        action: async () => this.openCreateDialog('file', prefix),
      },
      {
        name: 'Upload Files...',
        iconName: 'lucide:file',
        action: async () => this.triggerFileUpload(prefix, 'files'),
      },
      {
        name: 'Upload Folder...',
        iconName: 'lucide:folderUp',
        action: async () => this.triggerFileUpload(prefix, 'folder'),
      },
    ]);
  }

  private openCreateDialog(type: 'folder' | 'file', prefix: string) {
    this.createDialogType = type;
    this.createDialogPrefix = prefix;
    this.createDialogName = '';
    this.showCreateDialog = true;
  }

  private getContentType(ext: string): string {
    const contentTypes: Record<string, string> = {
      json: 'application/json',
      txt: 'text/plain',
      html: 'text/html',
      css: 'text/css',
      js: 'application/javascript',
      ts: 'text/typescript',
      md: 'text/markdown',
      xml: 'application/xml',
      yaml: 'text/yaml',
      yml: 'text/yaml',
      csv: 'text/csv',
    };
    return contentTypes[ext] || 'application/octet-stream';
  }

  private getDefaultContent(ext: string): string {
    const defaults: Record<string, string> = {
      json: '{\n  \n}',
      html: '<!DOCTYPE html>\n<html>\n<head>\n  <title></title>\n</head>\n<body>\n  \n</body>\n</html>',
      md: '# Title\n\n',
      txt: '',
    };
    return defaults[ext] || '';
  }

  // --- File upload helpers ---

  private ensureFileInputs(): void {
    if (!this.fileInputElement) {
      this.fileInputElement = document.createElement('input');
      this.fileInputElement.type = 'file';
      this.fileInputElement.multiple = true;
      this.fileInputElement.style.display = 'none';
      this.shadowRoot!.appendChild(this.fileInputElement);
    }
    if (!this.folderInputElement) {
      this.folderInputElement = document.createElement('input');
      this.folderInputElement.type = 'file';
      this.folderInputElement.multiple = true;
      (this.folderInputElement as any).webkitdirectory = true;
      this.folderInputElement.style.display = 'none';
      this.shadowRoot!.appendChild(this.folderInputElement);
    }
  }

  private triggerFileUpload(targetPrefix: string, type: 'files' | 'folder') {
    this.ensureFileInputs();
    const input = type === 'folder' ? this.folderInputElement! : this.fileInputElement!;
    input.value = '';
    const handler = async () => {
      input.removeEventListener('change', handler);
      if (input.files && input.files.length > 0) {
        await this.uploadFiles(Array.from(input.files), targetPrefix);
      }
    };
    input.addEventListener('change', handler);
    input.click();
  }

  private async uploadFiles(files: File[], targetPrefix: string) {
    this.uploading = true;
    this.uploadProgress = { current: 0, total: files.length };

    for (let i = 0; i < files.length; i++) {
      const file = files[i];
      this.uploadProgress = { current: i + 1, total: files.length };
      try {
        const base64 = await this.readFileAsBase64(file);
        // Use webkitRelativePath if available (folder upload), otherwise just filename
        const relativePath = (file as any).webkitRelativePath || file.name;
        const key = targetPrefix + relativePath;
        const contentType = file.type || this.getContentType(file.name.split('.').pop()?.toLowerCase() || '');
        await apiService.putObject(this.bucketName, key, base64, contentType);
      } catch (err) {
        console.error(`Failed to upload ${file.name}:`, err);
      }
    }

    this.uploading = false;
    this.uploadProgress = null;
    await this.refreshColumnByPrefix(targetPrefix);
  }

  private async uploadFilesWithPaths(
    files: { file: File; relativePath: string }[],
    targetPrefix: string
  ) {
    this.uploading = true;
    this.uploadProgress = { current: 0, total: files.length };

    for (let i = 0; i < files.length; i++) {
      const { file, relativePath } = files[i];
      this.uploadProgress = { current: i + 1, total: files.length };
      try {
        const base64 = await this.readFileAsBase64(file);
        const key = targetPrefix + relativePath;
        const contentType = file.type || this.getContentType(file.name.split('.').pop()?.toLowerCase() || '');
        await apiService.putObject(this.bucketName, key, base64, contentType);
      } catch (err) {
        console.error(`Failed to upload ${relativePath}:`, err);
      }
    }

    this.uploading = false;
    this.uploadProgress = null;
    await this.refreshColumnByPrefix(targetPrefix);
  }

  private readFileAsBase64(file: File): Promise<string> {
    return new Promise((resolve, reject) => {
      const reader = new FileReader();
      reader.onload = () => {
        const result = reader.result as string;
        resolve(result.split(',')[1] || '');
      };
      reader.onerror = () => reject(reader.error);
      reader.readAsDataURL(file);
    });
  }

  private async refreshColumnByPrefix(prefix: string) {
    const columnIndex = this.columns.findIndex(col => col.prefix === prefix);
    if (columnIndex === -1) {
      await this.refreshAllColumns();
      return;
    }
    try {
      const result = await apiService.listObjects(this.bucketName, prefix, '/');
      this.columns = this.columns.map((col, i) =>
        i === columnIndex ? { ...col, objects: result.objects, prefixes: result.prefixes } : col
      );
    } catch {
      await this.refreshAllColumns();
    }
  }

  // --- Drag-and-drop handlers ---

  private handleColumnDragEnter(e: DragEvent, columnIndex: number) {
    e.preventDefault();
    e.stopPropagation();
    const count = (this.dragCounters.get(columnIndex) || 0) + 1;
    this.dragCounters.set(columnIndex, count);
    this.dragOverColumnIndex = columnIndex;
  }

  private handleColumnDragOver(e: DragEvent) {
    e.preventDefault();
    e.stopPropagation();
    if (e.dataTransfer) {
      e.dataTransfer.dropEffect = this.draggedItem ? 'move' : 'copy';
    }
  }

  private handleColumnDragLeave(e: DragEvent, columnIndex: number) {
    e.stopPropagation();
    const count = (this.dragCounters.get(columnIndex) || 1) - 1;
    this.dragCounters.set(columnIndex, count);
    if (count <= 0) {
      this.dragCounters.delete(columnIndex);
      if (this.dragOverColumnIndex === columnIndex) this.dragOverColumnIndex = -1;
      this.clearFolderHover();
    }
  }

  private async handleColumnDrop(e: DragEvent, columnIndex: number) {
    e.preventDefault();
    e.stopPropagation();

    const targetPrefix = this.dragOverFolderPrefix ?? this.columns[columnIndex].prefix;

    // Check if this is an internal move (item from within the app)
    if (this.draggedItem) {
      this.initiateMove(this.draggedItem.key, this.draggedItem.isFolder, targetPrefix);
      this.draggedItem = null;
      this.clearFolderHover();
      this.dragCounters.clear();
      this.dragOverColumnIndex = -1;
      return;
    }

    this.dragCounters.clear();
    this.dragOverColumnIndex = -1;

    const items = e.dataTransfer?.items;
    if (!items || items.length === 0) return;

    this.clearFolderHover();

    // Collect all files (including from nested folders)
    const allFiles: { file: File; relativePath: string }[] = [];

    const processEntry = async (entry: FileSystemEntry, path: string): Promise<void> => {
      if (entry.isFile) {
        const fileEntry = entry as FileSystemFileEntry;
        const file = await new Promise<File>((resolve, reject) => {
          fileEntry.file(resolve, reject);
        });
        allFiles.push({ file, relativePath: path + file.name });
      } else if (entry.isDirectory) {
        const dirEntry = entry as FileSystemDirectoryEntry;
        const reader = dirEntry.createReader();
        const entries = await new Promise<FileSystemEntry[]>((resolve, reject) => {
          reader.readEntries(resolve, reject);
        });
        for (const childEntry of entries) {
          await processEntry(childEntry, path + entry.name + '/');
        }
      }
    };

    // Process all dropped items
    for (let i = 0; i < items.length; i++) {
      const item = items[i];
      if (item.kind === 'file') {
        const entry = (item as any).webkitGetAsEntry();
        if (entry) {
          await processEntry(entry, '');
        }
      }
    }

    if (allFiles.length > 0) {
      await this.uploadFilesWithPaths(allFiles, targetPrefix);
    }
  }

  private handleFolderDragEnter(e: DragEvent, folderPrefix: string) {
    e.stopPropagation();
    this.clearFolderHover();
    this.folderHoverTimer = setTimeout(() => {
      this.dragOverFolderPrefix = folderPrefix;
    }, 500);
  }

  private handleFolderDragLeave(e: DragEvent, folderPrefix: string) {
    e.stopPropagation();
    // Check if we're leaving to a child element - if so, don't cancel the hover
    const target = e.currentTarget as HTMLElement;
    const relatedTarget = e.relatedTarget as Node | null;
    if (relatedTarget && target.contains(relatedTarget)) {
      return; // Still inside the folder, ignore this dragleave
    }
    if (this.dragOverFolderPrefix === folderPrefix) this.dragOverFolderPrefix = null;
    if (this.folderHoverTimer) {
      clearTimeout(this.folderHoverTimer);
      this.folderHoverTimer = null;
    }
  }

  private handleFolderDragOver(e: DragEvent) {
    e.preventDefault();
    e.stopPropagation();
    if (e.dataTransfer) {
      e.dataTransfer.dropEffect = this.draggedItem ? 'move' : 'copy';
    }
  }

  private clearFolderHover() {
    if (this.folderHoverTimer) { clearTimeout(this.folderHoverTimer); this.folderHoverTimer = null; }
    this.dragOverFolderPrefix = null;
  }

  // --- Internal drag handlers for move ---

  private handleItemDragStart(e: DragEvent, key: string, isFolder: boolean) {
    this.draggedItem = { key, isFolder };
    e.dataTransfer?.setData('text/plain', key);
    e.dataTransfer!.effectAllowed = 'copyMove';  // Allow both for flexibility
    // Add visual feedback to dragged item
    const target = e.target as HTMLElement;
    target.classList.add('dragging');
  }

  private handleItemDragEnd(e: DragEvent) {
    this.draggedItem = null;
    const target = e.target as HTMLElement;
    target.classList.remove('dragging');
  }

  // --- Move picker dialog ---

  private async openMovePickerDialog(key: string, isFolder: boolean) {
    this.movePickerSource = { key, isFolder };
    this.movePickerCurrentPrefix = '';
    this.showMovePickerDialog = true;
    await this.loadMovePickerPrefixes('');
  }

  private async navigateMovePicker(prefix: string) {
    this.movePickerCurrentPrefix = prefix;
    await this.loadMovePickerPrefixes(prefix);
  }

  private async loadMovePickerPrefixes(prefix: string) {
    this.movePickerLoading = true;
    try {
      const result = await apiService.listObjects(this.bucketName, prefix, '/');
      this.movePickerPrefixes = result.prefixes;
    } catch {
      this.movePickerPrefixes = [];
    }
    this.movePickerLoading = false;
  }

  private selectMoveDestination(destPrefix: string) {
    if (!this.movePickerSource) return;
    this.closeMovePickerDialog();
    this.initiateMove(this.movePickerSource.key, this.movePickerSource.isFolder, destPrefix);
  }

  private closeMovePickerDialog() {
    this.showMovePickerDialog = false;
    this.movePickerSource = null;
    this.movePickerCurrentPrefix = '';
    this.movePickerPrefixes = [];
  }

  // --- Move confirmation dialog ---

  private initiateMove(sourceKey: string, isFolder: boolean, destPrefix: string) {
    const validation = validateMove(sourceKey, destPrefix);

    if (!validation.valid) {
      this.moveSource = { key: sourceKey, isFolder };
      this.moveDestination = destPrefix;
      this.moveError = validation.error!;
      this.showMoveDialog = true;
      return;
    }

    this.moveSource = { key: sourceKey, isFolder };
    this.moveDestination = destPrefix;
    this.moveError = null;
    this.showMoveDialog = true;
  }

  private async executeMove() {
    if (!this.moveSource) return;

    this.moveInProgress = true;

    try {
      const sourceName = getFileName(this.moveSource.key);
      const destKey = this.moveDestination + sourceName + (this.moveSource.isFolder ? '/' : '');

      let result: { success: boolean; error?: string };
      if (this.moveSource.isFolder) {
        result = await apiService.movePrefix(
          this.bucketName,
          this.moveSource.key,
          destKey
        );
      } else {
        result = await apiService.moveObject(
          this.bucketName,
          this.moveSource.key,
          destKey
        );
      }

      if (result.success) {
        this.closeMoveDialog();
        await this.refreshAllColumns();
      } else {
        this.moveError = result.error || 'Move operation failed';
      }
    } catch (err) {
      this.moveError = `Error: ${err}`;
    }

    this.moveInProgress = false;
  }

  private closeMoveDialog() {
    this.showMoveDialog = false;
    this.moveSource = null;
    this.moveDestination = '';
    this.moveError = null;
    this.moveInProgress = false;
  }

  // --- Rename dialog methods ---

  private openRenameDialog(key: string, isFolder: boolean) {
    this.renameSource = { key, isFolder };
    this.renameName = getFileName(key);
    this.renameError = null;
    this.showRenameDialog = true;

    // Auto-focus and smart selection
    this.updateComplete.then(() => {
      const input = this.shadowRoot?.querySelector('.rename-dialog-input') as HTMLInputElement;
      if (input) {
        input.focus();
        if (!isFolder) {
          const lastDot = this.renameName.lastIndexOf('.');
          if (lastDot > 0) {
            input.setSelectionRange(0, lastDot);
          } else {
            input.select();
          }
        } else {
          input.select();
        }
      }
    });
  }

  private async executeRename() {
    if (!this.renameSource || !this.renameName.trim()) return;

    const newName = this.renameName.trim();
    const currentName = getFileName(this.renameSource.key);

    if (newName === currentName) {
      this.renameError = 'Name is the same as current';
      return;
    }
    if (!newName) {
      this.renameError = 'Name cannot be empty';
      return;
    }
    if (newName.includes('/')) {
      this.renameError = 'Name cannot contain "/"';
      return;
    }

    this.renameInProgress = true;
    this.renameError = null;

    try {
      const parentPrefix = getParentPrefix(this.renameSource.key);
      const newKey = parentPrefix + newName + (this.renameSource.isFolder ? '/' : '');

      let result: { success: boolean; error?: string };
      if (this.renameSource.isFolder) {
        result = await apiService.movePrefix(this.bucketName, this.renameSource.key, newKey);
      } else {
        result = await apiService.moveObject(this.bucketName, this.renameSource.key, newKey);
      }

      if (result.success) {
        this.closeRenameDialog();
        await this.refreshAllColumns();
      } else {
        this.renameError = result.error || 'Rename failed';
      }
    } catch (err) {
      this.renameError = `Error: ${err}`;
    }
    this.renameInProgress = false;
  }

  private closeRenameDialog() {
    this.showRenameDialog = false;
    this.renameSource = null;
    this.renameName = '';
    this.renameError = null;
    this.renameInProgress = false;
  }

  private renderRenameDialog() {
    if (!this.showRenameDialog || !this.renameSource) return '';

    const isFolder = this.renameSource.isFolder;
    const title = isFolder ? 'Rename Folder' : 'Rename File';

    return html`
      <div class="dialog-overlay" @click=${() => this.closeRenameDialog()}>
        <div class="dialog" @click=${(e: Event) => e.stopPropagation()}>
          <div class="dialog-title">${title}</div>
          <div class="dialog-location">
            Location: ${this.bucketName}/${getParentPrefix(this.renameSource.key)}
          </div>
          ${this.renameError ? html`<div class="move-error">${this.renameError}</div>` : ''}
          <input
            type="text"
            class="dialog-input rename-dialog-input"
            placeholder=${isFolder ? 'folder-name' : 'filename.ext'}
            .value=${this.renameName}
            @input=${(e: InputEvent) => {
              this.renameName = (e.target as HTMLInputElement).value;
              this.renameError = null;
            }}
            @keydown=${(e: KeyboardEvent) => {
              if (e.key === 'Enter') this.executeRename();
              if (e.key === 'Escape') this.closeRenameDialog();
            }}
          />
          <div class="dialog-actions">
            <button class="dialog-btn dialog-btn-cancel"
                    @click=${() => this.closeRenameDialog()}
                    ?disabled=${this.renameInProgress}>Cancel</button>
            <button class="dialog-btn dialog-btn-create"
                    @click=${() => this.executeRename()}
                    ?disabled=${this.renameInProgress || !this.renameName.trim()}>
              ${this.renameInProgress ? 'Renaming...' : 'Rename'}
            </button>
          </div>
        </div>
      </div>
    `;
  }

  private renderMoveDialog() {
    if (!this.showMoveDialog || !this.moveSource) return '';

    const sourceName = getFileName(this.moveSource.key);

    return html`
      <div class="dialog-overlay" @click=${() => this.closeMoveDialog()}>
        <div class="dialog" @click=${(e: Event) => e.stopPropagation()}>
          <div class="dialog-title">Move ${this.moveSource.isFolder ? 'Folder' : 'File'}</div>

          ${this.moveError ? html`
            <div class="move-error">${this.moveError}</div>
          ` : html`
            <div class="move-summary">
              <div class="move-item">
                <span class="move-label">From:</span>
                <span class="move-path">${this.moveSource.key}</span>
              </div>
              <div class="move-item">
                <span class="move-label">To:</span>
                <span class="move-path">${this.moveDestination}${sourceName}</span>
              </div>
            </div>
          `}

          <div class="dialog-actions">
            <button class="dialog-btn dialog-btn-cancel"
                    @click=${() => this.closeMoveDialog()}
                    ?disabled=${this.moveInProgress}>
              Cancel
            </button>
            ${!this.moveError ? html`
              <button class="dialog-btn dialog-btn-create"
                      @click=${() => this.executeMove()}
                      ?disabled=${this.moveInProgress}>
                ${this.moveInProgress ? 'Moving...' : 'Move'}
              </button>
            ` : ''}
          </div>
        </div>
      </div>
    `;
  }

  private renderMovePickerDialog() {
    if (!this.showMovePickerDialog || !this.movePickerSource) return '';

    const sourceName = getFileName(this.movePickerSource.key);
    const sourceParent = getParentPrefix(this.movePickerSource.key);

    return html`
      <div class="dialog-overlay" @click=${() => this.closeMovePickerDialog()}>
        <div class="dialog move-picker-dialog" @click=${(e: Event) => e.stopPropagation()}>
          <div class="dialog-title">Move "${sourceName}" to...</div>

          <div class="picker-breadcrumb">
            <span class="picker-crumb" @click=${() => this.navigateMovePicker('')}>
              ${this.bucketName}
            </span>
            ${this.getPathSegments(this.movePickerCurrentPrefix).map(seg => html`
              <span class="picker-separator">/</span>
              <span class="picker-crumb" @click=${() => this.navigateMovePicker(seg)}>
                ${getFileName(seg)}
              </span>
            `)}
          </div>

          <div class="picker-list">
            ${this.movePickerLoading ? html`<div class="picker-empty">Loading...</div>` : ''}
            ${!this.movePickerLoading && this.movePickerPrefixes.filter(p => p !== this.movePickerSource!.key).length === 0 ? html`
              <div class="picker-empty">No subfolders</div>
            ` : ''}
            ${this.movePickerPrefixes
              .filter(p => p !== this.movePickerSource!.key) // Hide source from list
              .map(prefix => html`
                <div class="picker-item"
                     @click=${() => this.navigateMovePicker(prefix)}
                     @dblclick=${() => this.selectMoveDestination(prefix)}>
                  <svg class="icon" viewBox="0 0 24 24" fill="currentColor">
                    <path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>
                  </svg>
                  <span class="name">${getFileName(prefix)}</span>
                </div>
              `)}
          </div>

          <div class="dialog-actions">
            <button class="dialog-btn dialog-btn-cancel" @click=${() => this.closeMovePickerDialog()}>
              Cancel
            </button>
            <button class="dialog-btn dialog-btn-create"
                    @click=${() => this.selectMoveDestination(this.movePickerCurrentPrefix)}
                    ?disabled=${this.movePickerCurrentPrefix === sourceParent}>
              Move Here
            </button>
          </div>
        </div>
      </div>
    `;
  }

  private async handleCreate() {
    if (!this.createDialogName.trim()) return;

    const name = this.createDialogName.trim();
    let path: string;

    if (this.createDialogType === 'folder') {
      // Support deep paths: "a/b/c" creates nested folders
      path = this.createDialogPrefix + name + '/.keep';
    } else {
      path = this.createDialogPrefix + name;
    }

    const ext = name.split('.').pop()?.toLowerCase() || '';
    const contentType = this.createDialogType === 'file' ? this.getContentType(ext) : 'application/octet-stream';
    const content = this.createDialogType === 'file' ? this.getDefaultContent(ext) : '';

    const success = await apiService.putObject(
      this.bucketName,
      path,
      btoa(content),
      contentType
    );

    if (success) {
      this.showCreateDialog = false;
      await this.refreshColumnByPrefix(this.createDialogPrefix);
    }
  }

  private renderCreateDialog() {
    if (!this.showCreateDialog) return '';

    const isFolder = this.createDialogType === 'folder';
    const title = isFolder ? 'Create New Folder' : 'Create New File';
    const placeholder = isFolder ? 'folder-name or path/to/folder' : 'filename.txt or path/to/file.txt';

    return html`
      <div class="dialog-overlay" @click=${() => this.showCreateDialog = false}>
        <div class="dialog" @click=${(e: Event) => e.stopPropagation()}>
          <div class="dialog-title">${title}</div>
          <div class="dialog-location">
            Location: ${this.bucketName}/${this.createDialogPrefix}
          </div>
          <input
            type="text"
            class="dialog-input"
            placeholder=${placeholder}
            .value=${this.createDialogName}
            @input=${(e: InputEvent) => this.createDialogName = (e.target as HTMLInputElement).value}
            @keydown=${(e: KeyboardEvent) => e.key === 'Enter' && this.handleCreate()}
          />
          <div class="dialog-hint">
            Use "/" to create nested ${isFolder ? 'folders' : 'path'} (e.g., ${isFolder ? 'parent/child' : 'folder/file.txt'})
          </div>
          <div class="dialog-actions">
            <button class="dialog-btn dialog-btn-cancel" @click=${() => this.showCreateDialog = false}>
              Cancel
            </button>
            <button
              class="dialog-btn dialog-btn-create"
              ?disabled=${!this.createDialogName.trim()}
              @click=${() => this.handleCreate()}
            >
              Create
            </button>
          </div>
        </div>
      </div>
    `;
  }

  render() {
    if (this.loading && this.columns.length === 0) {
      return html`<div class="loading">Loading...</div>`;
    }

    return html`
      <div class="columns-container">
        ${this.columns.map((column, index) => this.renderColumnWrapper(column, index))}
      </div>
      ${this.renderCreateDialog()}
      ${this.renderMoveDialog()}
      ${this.renderMovePickerDialog()}
      ${this.renderRenameDialog()}
    `;
  }

  private renderColumnWrapper(column: IColumn, index: number) {
    return html`
      <div class="column-wrapper">
        ${this.renderColumn(column, index)}
        <div
          class="resize-handle ${this.resizing?.columnIndex === index ? 'active' : ''}"
          @mousedown=${(e: MouseEvent) => this.startResize(e, index)}
        ></div>
      </div>
    `;
  }

  private renderColumn(column: IColumn, index: number) {
    const headerName = column.prefix
      ? getFileName(column.prefix)
      : this.bucketName;

    return html`
      <div
        class="column ${this.dragOverColumnIndex === index ? 'drag-over' : ''}"
        style="width: ${column.width}px"
        @dragenter=${(e: DragEvent) => this.handleColumnDragEnter(e, index)}
        @dragover=${(e: DragEvent) => this.handleColumnDragOver(e)}
        @dragleave=${(e: DragEvent) => this.handleColumnDragLeave(e, index)}
        @drop=${(e: DragEvent) => this.handleColumnDrop(e, index)}
      >
        <div class="column-header" title=${column.prefix || this.bucketName}>
          ${headerName}
        </div>
        <div class="column-items" @contextmenu=${(e: MouseEvent) => this.handleEmptySpaceContextMenu(e, index)}>
          ${column.prefixes.length === 0 && column.objects.length === 0
            ? html`<div class="empty-state">Empty folder</div>`
            : ''}
          ${column.prefixes.map(
            (prefix) => html`
              <div
                class="column-item folder ${column.selectedItem === prefix ? 'selected' : ''} ${this.dragOverFolderPrefix === prefix ? 'drag-target' : ''}"
                draggable="true"
                @click=${() => this.selectFolder(index, prefix)}
                @contextmenu=${(e: MouseEvent) => this.handleFolderContextMenu(e, index, prefix)}
                @dragstart=${(e: DragEvent) => this.handleItemDragStart(e, prefix, true)}
                @dragend=${(e: DragEvent) => this.handleItemDragEnd(e)}
                @dragenter=${(e: DragEvent) => this.handleFolderDragEnter(e, prefix)}
                @dragover=${(e: DragEvent) => this.handleFolderDragOver(e)}
                @dragleave=${(e: DragEvent) => this.handleFolderDragLeave(e, prefix)}
              >
                <svg class="icon" viewBox="0 0 24 24" fill="currentColor">
                  <path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" />
                </svg>
                <span class="name">${getFileName(prefix)}</span>
                <svg class="chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                  <polyline points="9 18 15 12 9 6"></polyline>
                </svg>
              </div>
            `
          )}
          ${column.objects.map(
            (obj) => html`
              <div
                class="column-item ${column.selectedItem === obj.key ? 'selected' : ''}"
                draggable="true"
                @click=${() => this.selectFile(index, obj.key)}
                @contextmenu=${(e: MouseEvent) => this.handleFileContextMenu(e, index, obj.key)}
                @dragstart=${(e: DragEvent) => this.handleItemDragStart(e, obj.key, false)}
                @dragend=${(e: DragEvent) => this.handleItemDragEnd(e)}
              >
                <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                  <path d="${this.getFileIcon(obj.key)}" />
                </svg>
                <span class="name">${getFileName(obj.key)}</span>
              </div>
            `
          )}
        </div>
        ${this.dragOverColumnIndex === index ? html`
          <div class="drag-hint">
            ${this.draggedItem
              ? (this.dragOverFolderPrefix
                  ? `Move to ${getFileName(this.dragOverFolderPrefix)}`
                  : 'Move here')
              : (this.dragOverFolderPrefix
                  ? `Drop to upload into ${getFileName(this.dragOverFolderPrefix)}`
                  : 'Drop to upload here')}
          </div>
        ` : ''}
        ${this.uploading ? html`
          <div class="upload-overlay">
            <div class="upload-text">Uploading...</div>
            ${this.uploadProgress ? html`
              <div class="upload-progress">${this.uploadProgress.current} / ${this.uploadProgress.total} files</div>
            ` : ''}
          </div>
        ` : ''}
      </div>
    `;
  }
}
