/**
 * DSS Workflow State Machines
 *
 * State machine implementation for orchestrating multi-step workflows
 * with transition guards, side effects, and progress tracking.
 *
 * @module workflows
 */

import { notifySuccess, notifyError, notifyInfo, ErrorCode } from './messaging.js';
import router from './router.js';

/**
 * Base State Machine class
 */
class StateMachine {
  constructor(config) {
    this.config = config;
    this.currentState = config.initial;
    this.previousState = null;
    this.context = {};
    this.history = [];
    this.listeners = new Map();
  }

  /**
   * Get current state definition
   */
  getCurrentStateDefinition() {
    return this.config.states[this.currentState];
  }

  /**
   * Check if transition is allowed
   * @param {string} event - Event to transition on
   * @returns {string|null} Next state or null if not allowed
   */
  canTransition(event) {
    const stateDefinition = this.getCurrentStateDefinition();

    if (!stateDefinition || !stateDefinition.on) {
      return null;
    }

    return stateDefinition.on[event] || null;
  }

  /**
   * Send an event to trigger state transition
   * @param {string} event - Event name
   * @param {Object} [data] - Event data
   * @returns {Promise<boolean>} Whether transition succeeded
   */
  async send(event, data = {}) {
    const nextState = this.canTransition(event);

    if (!nextState) {
      console.warn(`No transition for event "${event}" in state "${this.currentState}"`);
      return false;
    }

    // Call exit actions for current state
    await this.callActions(this.getCurrentStateDefinition().exit, { event, data });

    // Store previous state
    this.previousState = this.currentState;

    // Transition to next state
    this.currentState = nextState;

    // Record history
    this.history.push({
      from: this.previousState,
      to: this.currentState,
      event,
      timestamp: new Date().toISOString(),
      data,
    });

    // Call entry actions for new state
    await this.callActions(this.getCurrentStateDefinition().entry, { event, data });

    // Emit state change
    this.emit('stateChange', {
      previous: this.previousState,
      current: this.currentState,
      event,
      data,
    });

    return true;
  }

  /**
   * Call state actions
   * @param {string[]|undefined} actions - Action names to execute
   * @param {Object} context - Action context
   */
  async callActions(actions, context) {
    if (!actions || !Array.isArray(actions)) {
      return;
    }

    for (const actionName of actions) {
      const action = this.config.actions?.[actionName];

      if (action) {
        try {
          await action.call(this, { ...context, machine: this });
        } catch (error) {
          console.error(`Action "${actionName}" failed:`, error);
        }
      }
    }
  }

  /**
   * Check if machine is in a specific state
   * @param {string} state - State name
   * @returns {boolean} Whether machine is in that state
   */
  isIn(state) {
    return this.currentState === state;
  }

  /**
   * Check if machine is in final state
   * @returns {boolean} Whether machine is in final state
   */
  isFinal() {
    const stateDefinition = this.getCurrentStateDefinition();
    return stateDefinition?.type === 'final' || false;
  }

  /**
   * Subscribe to machine events
   * @param {string} event - Event name
   * @param {Function} handler - Event handler
   * @returns {Function} Unsubscribe function
   */
  on(event, handler) {
    if (!this.listeners.has(event)) {
      this.listeners.set(event, []);
    }

    this.listeners.get(event).push(handler);

    // Return unsubscribe function
    return () => {
      const handlers = this.listeners.get(event);
      const index = handlers.indexOf(handler);
      if (index > -1) {
        handlers.splice(index, 1);
      }
    };
  }

  /**
   * Emit event to listeners
   * @param {string} event - Event name
   * @param {*} data - Event data
   */
  emit(event, data) {
    const handlers = this.listeners.get(event) || [];
    handlers.forEach(handler => {
      try {
        handler(data);
      } catch (error) {
        console.error(`Event handler error for "${event}":`, error);
      }
    });
  }

  /**
   * Get workflow progress
   * @returns {Object} Progress information
   */
  getProgress() {
    const states = Object.keys(this.config.states);
    const currentIndex = states.indexOf(this.currentState);
    const total = states.length;

    return {
      current: currentIndex + 1,
      total,
      percentage: Math.round(((currentIndex + 1) / total) * 100),
      state: this.currentState,
      isComplete: this.isFinal(),
    };
  }

  /**
   * Reset machine to initial state
   */
  reset() {
    this.currentState = this.config.initial;
    this.previousState = null;
    this.context = {};
    this.history = [];
    this.emit('reset', {});
  }

  /**
   * Get state history
   * @returns {Array} State transition history
   */
  getHistory() {
    return [...this.history];
  }
}

/**
 * Create Project Workflow
 *
 * Guides user through: Create Project → Configure Settings → Extract Tokens → Success
 */
export class CreateProjectWorkflow extends StateMachine {
  constructor(options = {}) {
    const config = {
      initial: 'init',
      states: {
        init: {
          on: {
            CREATE_PROJECT: 'creating',
          },
          entry: ['showCreateForm'],
        },
        creating: {
          on: {
            PROJECT_CREATED: 'created',
            CREATE_FAILED: 'init',
          },
          entry: ['createProject'],
        },
        created: {
          on: {
            CONFIGURE_SETTINGS: 'configuring',
            SKIP_CONFIG: 'ready',
          },
          entry: ['showSuccessMessage', 'promptConfiguration'],
        },
        configuring: {
          on: {
            SETTINGS_SAVED: 'ready',
            CONFIG_CANCELLED: 'created',
          },
          entry: ['navigateToSettings'],
        },
        ready: {
          on: {
            EXTRACT_TOKENS: 'extracting',
            RECONFIGURE: 'configuring',
          },
          entry: ['showReadyMessage'],
        },
        extracting: {
          on: {
            EXTRACTION_SUCCESS: 'complete',
            EXTRACTION_FAILED: 'ready',
          },
          entry: ['extractTokens'],
        },
        complete: {
          type: 'final',
          entry: ['showCompletionMessage', 'navigateToTokens'],
        },
      },
      actions: {
        showCreateForm: async ({ machine }) => {
          notifyInfo('Create a new project to get started');
          if (options.onShowCreateForm) {
            await options.onShowCreateForm(machine);
          }
        },

        createProject: async ({ data, machine }) => {
          machine.context.projectName = data.projectName;
          machine.context.projectId = data.projectId;

          if (options.onCreateProject) {
            await options.onCreateProject(machine.context);
          }
        },

        showSuccessMessage: async ({ machine }) => {
          notifySuccess(
            `Project "${machine.context.projectName}" created successfully!`,
            ErrorCode.SUCCESS_CREATED,
            { projectId: machine.context.projectId }
          );
        },

        promptConfiguration: async ({ machine }) => {
          notifyInfo(
            'Next: Configure your project settings (Figma key, description)',
            { duration: 7000 }
          );

          if (options.onPromptConfiguration) {
            await options.onPromptConfiguration(machine);
          }
        },

        navigateToSettings: async ({ machine }) => {
          router.navigate('figma');

          if (options.onNavigateToSettings) {
            await options.onNavigateToSettings(machine);
          }
        },

        showReadyMessage: async ({ machine }) => {
          notifyInfo('Project configured! Ready to extract tokens from Figma');

          if (options.onReady) {
            await options.onReady(machine);
          }
        },

        extractTokens: async ({ machine }) => {
          if (options.onExtractTokens) {
            await options.onExtractTokens(machine);
          }
        },

        showCompletionMessage: async ({ machine }) => {
          notifySuccess(
            'Tokens extracted successfully! Your design system is ready.',
            ErrorCode.SUCCESS_OPERATION
          );

          if (options.onComplete) {
            await options.onComplete(machine);
          }
        },

        navigateToTokens: async ({ machine }) => {
          router.navigate('tokens');
        },
      },
    };

    super(config);

    // Store options
    this.options = options;

    // Subscribe to progress updates
    this.on('stateChange', ({ current, previous }) => {
      const progress = this.getProgress();

      if (options.onProgress) {
        options.onProgress(current, progress);
      }

      // Emit to global event bus
      window.dispatchEvent(new CustomEvent('workflow-progress', {
        detail: {
          workflow: 'create-project',
          current,
          previous,
          progress,
        }
      }));
    });
  }

  /**
   * Start the workflow
   * @param {Object} data - Initial data
   */
  async start(data = {}) {
    this.reset();
    this.context = { ...data };
    await this.send('CREATE_PROJECT', data);
  }
}

/**
 * Token Extraction Workflow
 *
 * Guides through: Connect Figma → Select File → Extract → Sync
 */
export class TokenExtractionWorkflow extends StateMachine {
  constructor(options = {}) {
    const config = {
      initial: 'disconnected',
      states: {
        disconnected: {
          on: {
            CONNECT_FIGMA: 'connecting',
          },
          entry: ['promptFigmaConnection'],
        },
        connecting: {
          on: {
            CONNECTION_SUCCESS: 'connected',
            CONNECTION_FAILED: 'disconnected',
          },
          entry: ['testFigmaConnection'],
        },
        connected: {
          on: {
            SELECT_FILE: 'fileSelected',
            DISCONNECT: 'disconnected',
          },
          entry: ['showFileSelector'],
        },
        fileSelected: {
          on: {
            EXTRACT: 'extracting',
            CHANGE_FILE: 'connected',
          },
          entry: ['showExtractButton'],
        },
        extracting: {
          on: {
            EXTRACT_SUCCESS: 'extracted',
            EXTRACT_FAILED: 'fileSelected',
          },
          entry: ['performExtraction'],
        },
        extracted: {
          on: {
            SYNC: 'syncing',
            EXTRACT_AGAIN: 'extracting',
          },
          entry: ['showSyncOption'],
        },
        syncing: {
          on: {
            SYNC_SUCCESS: 'complete',
            SYNC_FAILED: 'extracted',
          },
          entry: ['performSync'],
        },
        complete: {
          type: 'final',
          entry: ['showSuccess'],
        },
      },
      actions: {
        promptFigmaConnection: async () => {
          notifyInfo('Connect to Figma to extract design tokens');
        },

        testFigmaConnection: async ({ data, machine }) => {
          if (options.onTestConnection) {
            await options.onTestConnection(data);
          }
        },

        showFileSelector: async () => {
          notifySuccess('Connected to Figma! Select a file to extract tokens.');
        },

        showExtractButton: async ({ data }) => {
          notifyInfo(`File selected: ${data.fileName || 'Unknown'}. Ready to extract.`);
        },

        performExtraction: async ({ data, machine }) => {
          if (options.onExtract) {
            await options.onExtract(data);
          }
        },

        showSyncOption: async () => {
          notifySuccess('Tokens extracted! Sync to your codebase?');
        },

        performSync: async ({ data }) => {
          if (options.onSync) {
            await options.onSync(data);
          }
        },

        showSuccess: async () => {
          notifySuccess('Workflow complete! Tokens are synced and ready to use.');
        },
      },
    };

    super(config);
    this.options = options;
  }

  async start(data = {}) {
    this.reset();
    await this.send('CONNECT_FIGMA', data);
  }
}

/**
 * Create workflow instances
 */
export function createProjectWorkflow(options) {
  return new CreateProjectWorkflow(options);
}

export function tokenExtractionWorkflow(options) {
  return new TokenExtractionWorkflow(options);
}

export { StateMachine };
export default {
  StateMachine,
  CreateProjectWorkflow,
  TokenExtractionWorkflow,
  createProjectWorkflow,
  tokenExtractionWorkflow,
};
