/**
 * PresetImportDialog — Dialog for importing scene presets from JSON file.
 *
 * Extends ApplicationV2 via HandlebarsApplicationMixin to provide FoundryVTT-native
 * dialog experience. Allows GM to upload a JSON file and choose between merge or replace modes.
 *
 * @module ui/gm/PresetImportDialog
 */

import { PresetImportExportManager } from '../../core/PresetImportExportManager.js';
import { isValidScenePreset } from '../../contracts/scene-preset.js';
import { escapeHtml } from '../../utils/html.js';

// Maximum file size: 5MB
const MAX_FILE_SIZE = 5 * 1024 * 1024;
// Allowed MIME types for JSON files
const ALLOWED_MIME_TYPES = new Set(['application/json', 'text/plain', '']);

// Conditional base class — test environment lacks foundry globals.
// At module load time in tests, foundry is undefined → fallback class is used.

/** @private */
const _AppBase =
  typeof foundry !== 'undefined' &&
  foundry.applications?.api?.HandlebarsApplicationMixin &&
  foundry.applications?.api?.ApplicationV2
    ? foundry.applications.api.HandlebarsApplicationMixin(
        foundry.applications.api.ApplicationV2
      )
    : class _FallbackApp {
        static DEFAULT_OPTIONS = {};
        static PARTS = {};
        get rendered() { return this._rendered ?? false; }
        set rendered(v) { this._rendered = v; }
        get element() { return this._element ?? null; }
        set element(v) { this._element = v; }
        async render() { this._rendered = true; }
        async close() { this._rendered = false; }
        async _prepareContext() { return {}; }
        _onRender() {}
        _onClose() {}
        _onPosition() {}
      };

/**
 * Import dialog for scene presets.
 * Provides file picker, mode selection (merge/replace), preview, and confirmation.
 */
export class PresetImportDialog extends _AppBase {
  /**
   * @param {object} options - Dialog options.
   * @param {import('../../foundry/FoundryAdapter.js').FoundryAdapter} options.adapter - Foundry adapter.
   * @param {import('../../core/ScenePresetManager.js').ScenePresetManager} options.scenePresetManager - Scene preset manager.
   */
  constructor(options = {}) {
    super(options);

    if (!options || typeof options !== 'object') {
      throw new TypeError('PresetImportDialog: options argument is required and must be an object');
    }
    if (!options.adapter || typeof options.adapter !== 'object') {
      throw new TypeError('PresetImportDialog: adapter option is required and must be an object');
    }
    if (!options.scenePresetManager || typeof options.scenePresetManager !== 'object') {
      throw new TypeError('PresetImportDialog: scenePresetManager option is required and must be an object');
    }

    this._adapter = options.adapter;
    this._scenePresetManager = options.scenePresetManager;
    this._exportManager = new PresetImportExportManager(this._adapter, this._scenePresetManager);

    // State
    /** @type {File|null} */
    this._selectedFile = null;
    /** @type {'merge'|'replace'} */
    this._mode = 'merge';
    /** @type {Array<{name: string, valid: boolean, error?: string}>} */
    this._previewItems = [];
    /** @type {boolean} */
    this._requiresConfirmation = false;
    
    // Concurrency guard for async file operations
    /** @type {boolean} */
    this._parsingInProgress = false;
    
    // Event listener tracking for cleanup
    /** @type {Array<{element: Element, type: string, listener: EventListener}>} */
    this._eventListeners = [];
  }

  static DEFAULT_OPTIONS = {
    id: 'scrying-pool-preset-import',
    classes: ['scrying-pool', 'dialog', 'preset-import'],
    window: { title: 'Import Scene Presets', resizable: false },
    position: { width: 500, height: 'auto' },
  };

  static PARTS = {
    dialog: {
      template: 'modules/scrying-pool/templates/preset-import.hbs',
    },
  };

  /**
   * Get data for template rendering.
   * @returns {Promise<object>} Template data.
   */
  async _prepareContext() {
    const existingPresetCount = this._scenePresetManager.list().length;

    return {
      existingPresetCount,
      hasExistingPresets: existingPresetCount > 0,
      mode: this._mode,
      previewItems: this._previewItems,
      requiresConfirmation: this._requiresConfirmation,
      selectedFileName: this._selectedFile?.name ?? null,
      mergeLabel: this._adapter.i18n.localize('scrying-pool.presetImport.importModeMerge'),
      replaceLabel: this._adapter.i18n.localize('scrying-pool.presetImport.importModeReplace'),
    };
  }

  /**
   * ApplicationV2 lifecycle — sets up event listeners on every render.
   * @inheritdoc
   */
  _onRender(context, options) {
    super._onRender?.(context, options);
    const root = this.element;
    if (!root) return;

    // Clean up previous event listeners to prevent memory leaks
    for (const { element, type, listener } of this._eventListeners) {
      element.removeEventListener(type, listener);
    }
    this._eventListeners = [];

    // File input change handler
    const fileInput = root.querySelector('.sp-file-input');
    if (fileInput) {
      const handler = (event) => this._onFileSelected(event);
      fileInput.addEventListener('change', handler);
      this._eventListeners.push({ element: fileInput, type: 'change', listener: handler });
    }

    // Mode radio button handlers
    const mergeRadio = root.querySelector('.sp-mode-merge');
    if (mergeRadio) {
      const handler = () => {
        this._mode = 'merge';
        this._requiresConfirmation = false;
        this.render();
      };
      mergeRadio.addEventListener('change', handler);
      this._eventListeners.push({ element: mergeRadio, type: 'change', listener: handler });
    }

    const replaceRadio = root.querySelector('.sp-mode-replace');
    if (replaceRadio) {
      const handler = () => {
        this._mode = 'replace';
        this._requiresConfirmation = true;
        this.render();
      };
      replaceRadio.addEventListener('change', handler);
      this._eventListeners.push({ element: replaceRadio, type: 'change', listener: handler });
    }

    // Import button click handler
    const importBtn = root.querySelector('.sp-import-btn:not(.sp-confirm-btn)');
    if (importBtn) {
      const handler = async (event) => {
        event.preventDefault();
        await this._onImport();
      };
      importBtn.addEventListener('click', handler);
      this._eventListeners.push({ element: importBtn, type: 'click', listener: handler });
    }

    // Confirm button handler (for replace mode)
    const confirmBtn = root.querySelector('.sp-confirm-btn');
    if (confirmBtn) {
      const handler = async (event) => {
        event.preventDefault();
        await this._onConfirmImport();
      };
      confirmBtn.addEventListener('click', handler);
      this._eventListeners.push({ element: confirmBtn, type: 'click', listener: handler });
    }

    // Cancel button handler
    const cancelBtn = root.querySelector('.sp-cancel-btn');
    if (cancelBtn) {
      const handler = (event) => {
        event.preventDefault();
        this.close();
      };
      cancelBtn.addEventListener('click', handler);
      this._eventListeners.push({ element: cancelBtn, type: 'click', listener: handler });
    }
  }

  /**
   * ApplicationV2 lifecycle — clean up event listeners when closed.
   * @inheritdoc
   */
  async _onClose(options) {
    await super._onClose?.(options);
    
    // Clean up event listeners
    for (const { element, type, listener } of this._eventListeners) {
      try {
        element.removeEventListener(type, listener);
      } catch {
        // Ignore errors during cleanup
      }
    }
    this._eventListeners = [];
    
    // Clean up any references
    this._exportManager = null;
    this._scenePresetManager = null;
    this._adapter = null;
    this._selectedFile = null;
    this._previewItems = [];
  }

  /**
   * Handles file selection.
   * Validates the file and shows preview.
   * @param {Event} event - Change event from file input.
   * @private
   */
  _onFileSelected(event) {
    const input = /** @type {HTMLInputElement} */ (event.target);
    if (!input.files || input.files.length === 0) {
      this._selectedFile = null;
      this._previewItems = [];
      this._requiresConfirmation = false;
      this.render();
      return;
    }

    const file = input.files[0];

    // Validate file type
    if (!ALLOWED_MIME_TYPES.has(file.type)) {
      // Check file extension as fallback
      const validExtensions = ['.json'];
      const hasValidExtension = validExtensions.some(ext => file.name.toLowerCase().endsWith(ext));
      if (!hasValidExtension) {
        if (this._adapter.notifications) {
          this._adapter.notifications.error('Please select a JSON file (.json extension required)');
        }
        // Clear the input so user can select again
        input.value = '';
        return;
      }
    }

    // Validate file size
    if (file.size > MAX_FILE_SIZE) {
      if (this._adapter.notifications) {
        this._adapter.notifications.error(`File is too large (${Math.round(file.size / 1024 / 1024)}MB). Maximum size is ${Math.round(MAX_FILE_SIZE / 1024 / 1024)}MB.`);
      }
      input.value = '';
      return;
    }

    this._selectedFile = file;
    this._previewItems = [];
    this._parseAndPreviewFile();
  }

  /**
   * Parses the selected file and generates a preview.
   * @private
   */
  async _parseAndPreviewFile() {
    if (this._parsingInProgress) return;
    this._parsingInProgress = true;

    try {
      if (!this._selectedFile) {
        this._previewItems = [];
        this._requiresConfirmation = false;
        this.render();
        return;
      }

      const content = await this._readFileAsText(this._selectedFile);
      const data = JSON.parse(content);

      // Validate structure
      this._exportManager.validateImportData(data);

      // Generate preview items
      this._previewItems = [];
      const existingNames = new Set(this._scenePresetManager.list().map(p => p.name));

      for (const [name, presetData] of Object.entries(data.presets || {})) {
        let valid = true;
        let error = undefined;

        // Check if preset name already exists (for merge mode preview)
        if (this._mode === 'merge' && existingNames.has(name)) {
          valid = false;
          error = this._adapter.i18n.localize('scrying-pool.presetImport.previewWillSkip');
        }

        // Validate preset structure for preview accuracy
        if (valid && presetData) {
          try {
            isValidScenePreset(presetData);
          } catch (err) {
            valid = false;
            error = escapeHtml(err instanceof Error ? err.message : String(err));
          }
        }

        this._previewItems.push({ name, valid, error });
      }

      // Determine if replace mode needs confirmation
      if (this._mode === 'replace' && existingNames.size > 0) {
        this._requiresConfirmation = true;
      } else if (this._mode === 'merge') {
        this._requiresConfirmation = false;
      }

      this.render();
    } catch (err) {
      const errorMsg = err instanceof Error ? err.message : String(err);
      this._previewItems = [{ name: this._selectedFile?.name ?? 'unknown', valid: false, error: escapeHtml(errorMsg) }];
      this._requiresConfirmation = false;
      this.render();
    } finally {
      this._parsingInProgress = false;
    }
  }

  /**
   * Reads a file as text.
   * @param {File} file - File to read.
   * @returns {Promise<string>} File content as text.
   * @private
   */
  _readFileAsText(file) {
    return new Promise((resolve, reject) => {
      const reader = new FileReader();
      reader.onload = (event) => {
        if (event.target && typeof event.target.result === 'string') {
          resolve(event.target.result);
        } else {
          reject(new Error('Failed to read file'));
        }
      };
      reader.onerror = () => {
        reject(new Error('File read error'));
      };
      reader.readAsText(file);
    });
  }

  /**
   * Handles the import action.
   * For merge mode, processes immediately. For replace mode, shows confirmation.
   * @returns {Promise<void>}
   * @private
   */
  async _onImport() {
    if (!this._selectedFile) {
      if (this._adapter.notifications) {
        this._adapter.notifications.warn(this._adapter.i18n.localize('scrying-pool.presetImport.selectFileFirst'));
      }
      return;
    }

    if (this._mode === 'replace') {
      // Show confirmation for replace mode
      this._requiresConfirmation = true;
      this.render();
      return;
    }

    // Direct import for merge mode
    await this._processImport();
  }

  /**
   * Handles confirmed import (after user confirms replace mode).
   * @returns {Promise<void>}
   * @private
   */
  async _onConfirmImport() {
    await this._processImport();
  }

  /**
   * Processes the import operation.
   * @returns {Promise<void>}
   * @private
   */
  async _processImport() {
    if (!this._selectedFile) {
      return;
    }

    const isReplaceMode = this._mode === 'replace';
    const btn = this.element?.querySelector(isReplaceMode ? '.sp-confirm-btn' : '.sp-import-btn');
    if (!btn) return;

    const originalLabel = btn.innerHTML;

    try {
      // Disable button and show loading state (using textContent for safety)
      btn.disabled = true;
      btn.textContent = '';
      const spinner = document.createElement('i');
      spinner.className = 'fas fa-spinner fa-spin';
      const text = document.createTextNode(' ' + this._adapter.i18n.localize('scrying-pool.presetImport.importing'));
      btn.appendChild(spinner);
      btn.appendChild(text);

      // Read and import file
      const content = await this._readFileAsText(this._selectedFile);
      const result = await this._exportManager.importPresets(content, this._mode);

      if (result.success) {
        if (this._adapter.notifications) {
          this._adapter.notifications.info(result.message);
        }
        this.close();
      } else {
        // Show errors
        const errorMessages = result.errors.map(e => escapeHtml(e)).join('\n');
        if (this._adapter.notifications) {
          this._adapter.notifications.error(this._adapter.i18n.localize('scrying-pool.presetImport.importFailed') + '\n' + errorMessages);
        }
      }
    } catch (err) {
      const errorMsg = err instanceof Error ? err.message : String(err);
      if (this._adapter.notifications) {
        this._adapter.notifications.error(this._adapter.i18n.localize('scrying-pool.presetImport.importFailed') + ': ' + escapeHtml(errorMsg));
      }
    } finally {
      btn.disabled = false;
      btn.innerHTML = originalLabel;
    }
  }
}
