/**
 * @file
 * FlowDrop Playground Integration for Drupal
 *
 * @description
 * Provides Drupal behavior for initializing and managing the FlowDrop playground.
 * This script automatically mounts playground instances for elements with the
 * [data-flowdrop-playground] attribute.
 *
 * Supports three display modes:
 * - "standalone": Embedded in the page container (default)
 * - "embedded": Embedded with a close button
 * - "modal": Full-screen modal overlay (recommended for immersive experience)
 *
 * Requires the shared FlowDrop library (flowdrop_ui_components/flowdrop).
 *
 * @param {Function} once The "once" deduplication library.
 * @param {object} Drupal The Drupal API namespace.
 * @param {object} drupalSettings The Drupal settings object.
 */

((once, Drupal, drupalSettings) => {
  /**
   * Valid display modes for the playground
   *
   * @type {string[]}
   */
  const VALID_MODES = ['standalone', 'embedded', 'modal'];

  /**
   * Drupal behavior for mounting FlowDrop playground
   *
   * @type {object}
   */
  Drupal.behaviors.flowdropPlayground = {
    /**
     * Attach behavior to playground containers
     *
     * @param {HTMLElement} context - The context element to search within
     */
    attach(context) {
      once(
        'flowdrop-playground',
        '[data-flowdrop-playground]',
        context,
      ).forEach((playgroundContainer) => {
        initializePlayground(playgroundContainer);
      });
    },

    /**
     * Detach behavior and cleanup mounted components
     *
     * @param {HTMLElement} context - The context element to search within
     * @param {object} settings - Drupal settings object
     * @param {string} trigger - The trigger for detachment
     */
    detach(context, settings, trigger) {
      if (trigger !== 'unload') {
        return;
      }

      const containers = context.querySelectorAll('[data-flowdrop-playground]');
      containers.forEach((playgroundContainer) => {
        cleanupPlayground(playgroundContainer);
      });
    },
  };

  /**
   * Initialize the playground in the given container
   *
   * @param {HTMLElement} playgroundContainer - The container element for the playground
   */
  function initializePlayground(playgroundContainer) {
    if (playgroundContainer.dataset.flowdropInitialized === 'true') {
      return;
    }

    // Apply FlowDrop theme (light/dark/auto) to document so design-tokens
    // [data-theme="dark"] rules apply. Uses initializeSettings() which
    // internally calls initializeTheme() (initializeTheme is tree-shaken
    // from the bundle exports but initializeSettings is available).
    // Do NOT pass `defaults` — that path deep-merges over the user's
    // persisted preference and writes it back, clobbering their choice.
    if (
      typeof window.FlowDrop !== 'undefined' &&
      typeof window.FlowDrop.initializeSettings === 'function'
    ) {
      window.FlowDrop.initializeSettings();
    }

    // Check if shared FlowDrop library is available
    if (typeof window.FlowDrop === 'undefined') {
      showError(
        playgroundContainer,
        'FlowDrop Library Not Available',
        'The FlowDrop library could not be loaded. Please ensure flowdrop_ui_components module is enabled.',
      );
      return;
    }

    if (
      typeof window.FlowDrop.mountPlaygroundStudio !== 'function' &&
      typeof window.FlowDrop.mountPlayground !== 'function'
    ) {
      showError(
        playgroundContainer,
        'Playground mount function not available',
        'The mountPlayground function is not available in the FlowDrop library.',
      );
      return;
    }

    // Parse workflow data
    const workflow =
      parseJsonData(playgroundContainer.dataset.workflow) ||
      drupalSettings.flowdrop?.workflow;

    // Parse endpoint configuration
    let endpointConfig =
      parseJsonData(playgroundContainer.dataset.endpointConfig) ||
      drupalSettings.flowdrop?.endpointConfig;

    if (!endpointConfig) {
      const baseUrl = drupalSettings.flowdrop?.apiBaseUrl || '/api/flowdrop';
      endpointConfig = createDefaultEndpointConfig(baseUrl);
    }

    // Get configuration from data attributes
    let mode = playgroundContainer.dataset.mode || 'standalone';
    const backUrl =
      playgroundContainer.dataset.backUrl || '/admin/flowdrop/workflows';
    const workflowName = playgroundContainer.dataset.workflowName || 'Workflow';
    const showNavbar = playgroundContainer.dataset.showNavbar === 'true';
    const navbarTasks =
      parseJsonData(playgroundContainer.dataset.navbarTasks) || [];
    const navbarMenuActions =
      parseJsonData(playgroundContainer.dataset.navbarActions) || [];
    const navbarTitle = playgroundContainer.dataset.navbarTitle || workflowName;
    const uiConfig = parseUiConfig(playgroundContainer.dataset.uiConfig);
    const initialSessionId =
      playgroundContainer.dataset.initialSessionId ||
      drupalSettings.flowdrop?.initialSessionId ||
      null;

    // Chat input configuration options (new in FlowDrop 0.0.40).
    // showChatInput: Hide text input to show only Run button.
    // showRunButton: Hide Run button for view-only mode.
    // predefinedMessage: Custom message sent when Run is clicked.
    // autoRun: Auto-execute workflow on load.
    const showChatInput = playgroundContainer.dataset.showChatInput !== 'false';
    const showRunButton = playgroundContainer.dataset.showRunButton !== 'false';
    const predefinedMessage =
      playgroundContainer.dataset.predefinedMessage || null;
    const autoRun = playgroundContainer.dataset.autoRun === 'true';

    // Session UI gating (default true). Each flag controls a different slice
    // of the ControlPanel session chip; the Drupal single-session route flips
    // the inner two off to lock the user to the session in the URL.
    const showSessionHeader =
      playgroundContainer.dataset.showSessionHeader !== 'false';
    const showNewSessionButton =
      playgroundContainer.dataset.showNewSessionButton !== 'false';
    const showSessionList =
      playgroundContainer.dataset.showSessionList !== 'false';

    // Pipeline panel open state — defaults to true for the integrated studio experience.
    // Set data-initial-pipeline-open="false" to start with the panel collapsed.
    const initialPipelineOpen =
      playgroundContainer.dataset.initialPipelineOpen !== 'false';

    // Validate mode
    if (VALID_MODES.indexOf(mode) === -1) {
      mode = 'standalone';
    }

    // Clear loading state for non-modal modes
    if (mode !== 'modal') {
      // Loading element may be in the content container
      const contentContainer = playgroundContainer.querySelector(
        '.fd-playground__content',
      );
      const loadingElement = contentContainer
        ? contentContainer.querySelector('.fd-playground__loading')
        : playgroundContainer.querySelector('.fd-playground__loading');
      if (loadingElement) {
        loadingElement.style.display = 'none';
      }
    }

    // Mount the playground with the specified mode
    mountPlayground(playgroundContainer, {
      workflow,
      endpointConfig,
      mode,
      backUrl,
      workflowName,
      navbarTitle,
      navbarTasks,
      navbarMenuActions,
      showNavbar,
      uiConfig,
      initialSessionId,
      showChatInput,
      showRunButton,
      predefinedMessage,
      autoRun,
      initialPipelineOpen,
      showSessionHeader,
      showNewSessionButton,
      showSessionList,
    });
  }

  /**
   * Parse JSON data from a string, returning null on failure
   *
   * @param {string} jsonString - JSON string to parse
   * @return {object|null} Parsed object or null
   */
  function parseJsonData(jsonString) {
    if (!jsonString) return null;
    try {
      return JSON.parse(jsonString);
    } catch (error) {
      return null;
    }
  }

  /**
   * Build a FlowDrop AuthProvider from the Drupal-supplied endpoint config.
   *
   * FlowDrop 2.0 no longer reads `endpointConfig.auth`; authentication is a
   * pluggable AuthProvider passed at mount time (defaulting to no auth). The
   * Drupal endpoint config service still ships the CSRF header in
   * `endpointConfig.auth.headers` — translate it into a provider so write
   * requests (PUT/POST/DELETE) carry the `X-CSRF-Token` Drupal requires.
   *
   * The provider only needs `getAuthHeaders()`; `isAuthenticated()` was
   * removed from the AuthProvider interface in a later 2.0 build, and request
   * auth is driven entirely by the headers (plus optional 401/403 hooks).
   *
   * @param {object} endpointConfig - The endpoint configuration.
   * @return {object|undefined} An AuthProvider, or undefined when no headers.
   */
  function buildAuthProvider(endpointConfig) {
    const headers = endpointConfig?.auth?.headers;
    if (!headers || Object.keys(headers).length === 0) {
      return undefined;
    }
    return {
      getAuthHeaders: async () => headers,
    };
  }

  /**
   * Parse the ui_config JSON blob from data-ui-config.
   *
   * Returns { defaults, exposed } where:
   *  - defaults: PartialSettings forwarded as `settings` to the mount call.
   *  - exposed:  string[] of category names forwarded as `settingsCategories`.
   * Missing keys are returned as undefined so callers can spread without
   * overriding library defaults.
   *
   * @param {string|undefined} raw
   *   The raw data-ui-config attribute value, or undefined when unset.
   * @return {{defaults: object|undefined, exposed: string[]|undefined}}
   *   The parsed defaults and exposed category names; missing keys are
   *   undefined so callers can spread without overriding library defaults.
   */
  function parseUiConfig(raw) {
    const parsed = parseJsonData(raw);
    if (!parsed || typeof parsed !== 'object') {
      return { defaults: undefined, exposed: undefined };
    }
    const defaults =
      parsed.defaults && typeof parsed.defaults === 'object'
        ? parsed.defaults
        : undefined;
    const exposed = Array.isArray(parsed.exposed) ? parsed.exposed : undefined;
    return { defaults, exposed };
  }

  /**
   * Mount the FlowDrop playground component
   *
   * @param {HTMLElement} container - The container element
   * @param {object} options - Mount options including mode, workflow, and endpointConfig
   */
  async function mountPlayground(container, options) {
    // Set initialized flag immediately to prevent duplicate initialization
    container.dataset.flowdropInitialized = 'true';
    try {
      // Extract workflow ID - this is required by mountPlayground
      const workflowId = options.workflow ? options.workflow.id : null;

      if (!workflowId) {
        throw new Error('Workflow ID is required');
      }

      // Prepare workflow data object (optional, for pre-populating data)
      const workflowData = options.workflow
        ? {
            id: options.workflow.id,
            name:
              options.workflow.label ||
              options.workflow.name ||
              'Untitled Workflow',
            description: options.workflow.description || '',
            nodes: options.workflow.nodes || [],
            edges: options.workflow.edges || [],
            metadata: {
              ...(options.workflow.metadata || {}),
              schemaVersion: '1.0.0',
              created: options.workflow.created,
              changed: options.workflow.changed,
            },
          }
        : null;

      // Determine the display mode
      const mode = options.mode || 'standalone';
      const backUrl = options.backUrl || '/admin/flowdrop/workflows';

      /**
       * Close handler for modal and embedded modes.
       * Navigates to the back URL when the playground is closed.
       */
      const handleClose = () => {
        // Cleanup the playground instance
        cleanupPlayground(container);

        // Navigate to the back URL
        if (backUrl) {
          window.location.href = backUrl;
        }

        // Dispatch close event for custom handling
        container.dispatchEvent(
          new CustomEvent('flowdrop:playground:close', {
            detail: { containerId: container.id, workflowId },
            bubbles: true,
          }),
        );
      };

      // Build mount options based on mode.
      // Intentionally do NOT pass height/width — the SDC CSS owns container
      // sizing (definite height via `calc(100dvh - toolbar)` in playground.css),
      // and the library must not overwrite that with inline `height: 100%`,
      // which doesn't resolve against an indefinite parent.
      const mountOptions = {
        workflowId,
        workflow: workflowData,
        endpointConfig: options.endpointConfig,
        authProvider: buildAuthProvider(options.endpointConfig),
        mode,
        initialSessionId: options.initialSessionId || null,
        // PlaygroundStudio: pipeline panel open state (defaults to true).
        initialPipelineOpen: options.initialPipelineOpen !== false,
        // Chat input configuration (FlowDrop 0.0.40+).
        // These options must be passed inside the config object.
        config: {
          showChatInput: options.showChatInput !== false,
          showRunButton: options.showRunButton !== false,
          predefinedMessage: options.predefinedMessage || null,
          autoRun: options.autoRun === true,
          // Session UI gating — see playground.twig data-show-session-*
          // attributes. Defaults to true to match the library defaults.
          showSessionHeader: options.showSessionHeader !== false,
          showNewSessionButton: options.showNewSessionButton !== false,
          showSessionList: options.showSessionList !== false,
        },
        // Session status change callback (FlowDrop 0.0.61+).
        // Used to restart polling after interrupt resolution and
        // dispatch events for other Drupal behaviors.
        onSessionStatusChange(status, previousStatus) {
          handleSessionStatusChange(container, status, previousStatus);
        },
      };

      // Add onClose callback for modal and embedded modes
      if (mode === 'modal' || mode === 'embedded') {
        mountOptions.onClose = handleClose;
      }

      let app;
      if (
        options.showNavbar &&
        mode !== 'modal' &&
        typeof window.FlowDrop.mountPlaygroundApp === 'function'
      ) {
        // FlowDrop navbar mode: mount PlaygroundApp (Navbar + PlaygroundStudio)
        // directly into the outer container. The Svelte component manages its
        // own internal layout, so no .fd-playground__content wrapper is needed.
        //
        // The Svelte component is Drupal-agnostic: it accepts a `primaryActions`
        // array. Build it from the local-task-derived links (editor + any
        // contrib tabs) followed by the local-action-derived links (Pipelines,
        // Back to Workflows, and any contrib actions).
        const primaryActions = [];
        (options.navbarTasks || []).forEach((task) => {
          if (task && task.label && task.href) {
            primaryActions.push({
              label: task.label,
              href: task.href,
              variant: 'secondary',
              group: 'Tasks',
            });
          }
        });
        (options.navbarMenuActions || []).forEach((action) => {
          if (action && action.label && action.href) {
            primaryActions.push({
              label: action.label,
              href: action.href,
              variant: 'outline',
              group: 'Actions',
            });
          }
        });

        // The Svelte component no longer prefixes with "Playground / "; do it
        // here so the navbar reads "Playground / {workflow name}".
        const titleSource = options.navbarTitle || options.workflowName;
        const navbarTitle = titleSource
          ? `Playground / ${titleSource}`
          : 'Playground';

        // An explicit empty `exposed` list means "hide the gear icon" — there
        // is nothing to configure. An undefined list means "no Drupal config",
        // and the library default (all categories) applies.
        const exposed = options.uiConfig?.exposed;
        const hideSettings = Array.isArray(exposed) && exposed.length === 0;

        const appOptions = {
          ...mountOptions,
          showNavbar: true,
          navbarTitle,
          primaryActions,
          settings: options.uiConfig?.defaults,
          settingsCategories: exposed,
          ...(hideSettings ? { showSettings: false } : {}),
        };
        app = await window.FlowDrop.mountPlaygroundApp(container, appOptions);
      } else {
        // Legacy dark-header mode: mount PlaygroundStudio into the inner
        // .fd-playground__content container (below the static HTML header).
        const mountTarget =
          container.querySelector('.fd-playground__content') || container;

        // Mount PlaygroundStudio (integrated pipeline + chat) when available,
        // falling back to the plain Playground for older library versions.
        const mountFn =
          window.FlowDrop.mountPlaygroundStudio ??
          window.FlowDrop.mountPlayground;
        app = await mountFn(mountTarget, mountOptions);
      }

      container.flowdropApp = app;

      container.dispatchEvent(
        new CustomEvent('flowdrop:playground:init', {
          detail: {
            containerId: container.id,
            app,
            workflowId,
            mode,
          },
          bubbles: true,
        }),
      );
    } catch (error) {
      // Clear initialization flag so retry is possible
      container.removeAttribute('data-flowdrop-initialized');
      showError(
        container,
        'Failed to load playground',
        `An error occurred while initializing the playground: ${error.message}`,
      );
    }
  }

  /**
   * Handle session status changes from the playground.
   *
   * Dispatches a custom event for other Drupal behaviors to react to,
   * and restarts polling when the workflow resumes after interrupt resolution.
   *
   * @param {HTMLElement} container - The playground container
   * @param {string} status - The new session status
   * @param {string} previousStatus - The previous session status
   */
  function handleSessionStatusChange(container, status, previousStatus) {
    const app = container.flowdropApp;
    if (!app) return;

    container.dispatchEvent(
      new CustomEvent('flowdrop:playground:status-change', {
        detail: { status, previousStatus },
        bubbles: true,
      }),
    );

    // Restart polling when workflow resumes after interrupt resolution.
    // The backend changes session status from awaiting_input → running when
    // an interrupt is resolved, but polling has already stopped. We need to
    // restart it so the UI picks up new messages.
    if (status === 'running' && previousStatus === 'awaiting_input') {
      app.startPolling();
    }
  }

  /**
   * Cleanup playground instance
   *
   * @param {HTMLElement} container - The playground container
   */
  function cleanupPlayground(container) {
    if (
      container.flowdropApp &&
      typeof container.flowdropApp.destroy === 'function'
    ) {
      container.flowdropApp.destroy();
      container.flowdropApp = null;
    }

    container.removeAttribute('data-flowdrop-initialized');
  }

  /**
   * Create default endpoint configuration
   *
   * @param {string} baseUrl - The base URL for API endpoints
   * @return {object} Default endpoint configuration
   */
  function createDefaultEndpointConfig(baseUrl) {
    return {
      baseUrl,
      endpoints: {
        nodes: {
          list: '/nodes',
          get: '/nodes/{id}',
          byCategory: '/nodes?category={category}',
          metadata: '/nodes/{id}/metadata',
        },
        workflows: {
          list: '/workflows',
          get: '/workflows/{id}',
          create: '/workflows',
          update: '/workflows/{id}',
          delete: '/workflows/{id}',
          validate: '/workflows/validate',
          export: '/workflows/{id}/export',
          import: '/workflows/import',
        },
        playground: {
          sessions: '/workflows/{id}/playground/sessions',
          session: '/playground/sessions/{sessionId}',
          messages: '/playground/sessions/{sessionId}/messages',
          stop: '/playground/sessions/{sessionId}/stop',
          reset: '/playground/sessions/{sessionId}/reset',
        },
        interrupts: {
          list: '/playground/sessions/{sessionId}/interrupts',
          get: '/interrupts/{interruptId}',
          resolve: '/interrupts/{interruptId}',
          cancel: '/interrupts/{interruptId}/cancel',
        },
      },
      timeout: 30000,
      retry: {
        enabled: true,
        maxAttempts: 3,
        delay: 1000,
        backoff: 'exponential',
      },
    };
  }

  /**
   * Display an error message in the playground container
   *
   * @param {HTMLElement} container - The container element
   * @param {string} title - Error title
   * @param {string} message - Error message details
   */
  function showError(container, title, message) {
    const loadingElement = container.querySelector('.fd-playground__loading');
    if (loadingElement) {
      loadingElement.remove();
    }

    // Clear container safely
    while (container.firstChild) {
      container.removeChild(container.firstChild);
    }

    // Build error UI with safe DOM construction
    const errorDiv = document.createElement('div');
    errorDiv.className = 'fd-playground__error';
    errorDiv.setAttribute('role', 'alert');
    errorDiv.setAttribute('aria-live', 'assertive');

    const iconDiv = document.createElement('div');
    iconDiv.className = 'fd-playground__error-icon';
    const svgNS = 'http://www.w3.org/2000/svg';
    const svg = document.createElementNS(svgNS, 'svg');
    svg.setAttribute('xmlns', svgNS);
    svg.setAttribute('width', '48');
    svg.setAttribute('height', '48');
    svg.setAttribute('viewBox', '0 0 24 24');
    svg.setAttribute('fill', 'none');
    svg.setAttribute('stroke', 'currentColor');
    svg.setAttribute('stroke-width', '2');
    const circle = document.createElementNS(svgNS, 'circle');
    circle.setAttribute('cx', '12');
    circle.setAttribute('cy', '12');
    circle.setAttribute('r', '10');
    svg.appendChild(circle);
    const line1 = document.createElementNS(svgNS, 'line');
    line1.setAttribute('x1', '12');
    line1.setAttribute('y1', '8');
    line1.setAttribute('x2', '12');
    line1.setAttribute('y2', '12');
    svg.appendChild(line1);
    const line2 = document.createElementNS(svgNS, 'line');
    line2.setAttribute('x1', '12');
    line2.setAttribute('y1', '16');
    line2.setAttribute('x2', '12.01');
    line2.setAttribute('y2', '16');
    svg.appendChild(line2);
    iconDiv.appendChild(svg);
    errorDiv.appendChild(iconDiv);

    const h3 = document.createElement('h3');
    h3.className = 'fd-playground__error-title';
    h3.textContent = Drupal.checkPlain(title);
    errorDiv.appendChild(h3);

    const p = document.createElement('p');
    p.className = 'fd-playground__error-message';
    p.textContent = Drupal.checkPlain(message);
    errorDiv.appendChild(p);

    const hint = document.createElement('p');
    hint.className = 'fd-playground__error-hint';
    hint.textContent =
      'Please refresh the page or contact support if the problem persists.';
    errorDiv.appendChild(hint);

    container.appendChild(errorDiv);
    container.classList.add('fd-playground--error');
  }

  /**
   * Public API for FlowDrop Playground
   */
  Drupal.flowdropPlayground = {
    /**
     * Get the playground app instance
     *
     * @param {string|HTMLElement} idOrElement - Container ID or element
     * @return {object|null} The playground app instance or null
     */
    getApp(idOrElement) {
      const container =
        typeof idOrElement === 'string'
          ? document.getElementById(idOrElement)
          : idOrElement;
      return container?.flowdropApp || null;
    },

    /**
     * Destroy a playground instance
     *
     * @param {string|HTMLElement} idOrElement - Container ID or element
     */
    destroy(idOrElement) {
      const container =
        typeof idOrElement === 'string'
          ? document.getElementById(idOrElement)
          : idOrElement;
      if (container) {
        cleanupPlayground(container);
      }
    },

    /**
     * Open a playground in modal mode programmatically.
     * This creates a temporary container and mounts the playground as a full-screen modal.
     *
     * @param {object} options - Configuration options
     * @param {object} options.workflow - Workflow data object with id, nodes, edges, etc.
     * @param {object} options.endpointConfig - API endpoint configuration
     * @param {string} options.backUrl - URL to navigate to when modal is closed
     * @param {Function} options.onClose - Optional callback when modal is closed (called before navigation)
     * @return {Promise<object>} The mounted playground app instance
     */
    async openModal(options) {
      if (
        typeof window.FlowDrop === 'undefined' ||
        typeof window.FlowDrop.mountPlayground !== 'function'
      ) {
        throw new Error('FlowDrop library is not available');
      }

      if (!options.workflow || !options.workflow.id) {
        throw new Error('Workflow with id is required');
      }

      // Create a temporary container element for tracking
      const containerId = `flowdrop-playground-modal-${options.workflow.id}`;
      const existingContainer = document.getElementById(containerId);
      if (existingContainer) {
        // Close existing modal first
        cleanupPlayground(existingContainer);
        existingContainer.remove();
      }

      const container = document.createElement('div');
      container.id = containerId;
      container.setAttribute('data-flowdrop-playground', '');
      container.setAttribute('data-mode', 'modal');
      container.style.display = 'none'; // Hidden since modal mounts to body
      document.body.appendChild(container);

      // Prepare workflow data
      const workflowData = {
        id: options.workflow.id,
        name:
          options.workflow.label ||
          options.workflow.name ||
          'Untitled Workflow',
        description: options.workflow.description || '',
        nodes: options.workflow.nodes || [],
        edges: options.workflow.edges || [],
        metadata: options.workflow.metadata || {},
      };

      // Handle close action
      const handleClose = () => {
        // Call custom onClose callback if provided
        if (typeof options.onClose === 'function') {
          options.onClose();
        }

        // Cleanup
        cleanupPlayground(container);
        container.remove();

        // Navigate if backUrl is provided
        if (options.backUrl) {
          window.location.href = options.backUrl;
        }
      };

      // Mount in modal mode
      const app = await window.FlowDrop.mountPlayground(container, {
        workflowId: options.workflow.id,
        workflow: workflowData,
        endpointConfig: options.endpointConfig,
        authProvider: buildAuthProvider(options.endpointConfig),
        mode: 'modal',
        onClose: handleClose,
      });

      container.dataset.flowdropInitialized = 'true';
      container.flowdropApp = app;

      return app;
    },
  };
})(once, Drupal, drupalSettings);
