const MenuUtils = require('./menuUtils');

/**
 * Server-side helper for exposing MenuUtils to the browser via HTTP endpoints.
 */
class EndpointUtils {
  /**
   * @param {Object} options
   * @param {Function} options.MenuUtilsClass  the MenuUtils constructor/function
   */
  constructor({ MenuUtilsClass = MenuUtils } = {}) {
    this.MenuUtils = MenuUtilsClass;
  }

  /**
   * Registers an HTTP GET endpoint that serves the client-side MenuUtils code
   * @param {object} RED          the Node-RED API object
   * @param {string} nodeName     the name of the node (used in the URL)
   * @param {object} customHelpers  additional helper functions to inject
   */
  createMenuUtilsEndpoint(RED, nodeName, customHelpers = {}, options = {}) {
    const basePath = `/${nodeName}/resources`;

    RED.httpAdmin.get(`${basePath}/menuUtilsData.json`, (req, res) => {
      res.json(this.generateMenuUtilsData(nodeName, customHelpers, options));
    });

    RED.httpAdmin.get(`${basePath}/menuUtils.legacy.js`, (req, res) => {
      res.set('Content-Type', 'application/javascript');
      res.send(this.generateLegacyMenuUtilsCode(nodeName, customHelpers));
    });

    RED.httpAdmin.get(`${basePath}/menuUtils.js`, (req, res) => {
      res.set('Content-Type', 'application/javascript');
      res.send(this.generateMenuUtilsBootstrap(nodeName));
    });
  }

  generateMenuUtilsData(nodeName, customHelpers = {}, options = {}) {
    const defaultHelpers = {
      validateRequired: `function(value) {
        return value != null && value.toString().trim() !== '';
      }`,
      formatDisplayValue: `function(value, unit) {
        return \`${'${'}value} ${'${'}unit || ''}\`.trim();
      }`,
      validateScaling: `function(min, max) {
        return !isNaN(min) && !isNaN(max) && Number(min) < Number(max);
      }`,
      validateUnit: `function(unit) {
        return typeof unit === 'string' && unit.trim() !== '';
      }`,
    };

    return {
      nodeName,
      helpers: { ...defaultHelpers, ...customHelpers },
      options: {
        autoLoadLegacy: options.autoLoadLegacy !== false,
      },
    };
  }

  generateMenuUtilsBootstrap(nodeName) {
    return `
// Stable bootstrap for EVOLV menu utils (${nodeName})
(function() {
  const nodeName = ${JSON.stringify(nodeName)};
  const basePath = '/' + nodeName + '/resources';

  window.EVOLV = window.EVOLV || {};
  window.EVOLV.nodes = window.EVOLV.nodes || {};
  window.EVOLV.nodes[nodeName] = window.EVOLV.nodes[nodeName] || {};
  window.EVOLV.nodes[nodeName].utils = window.EVOLV.nodes[nodeName].utils || {};

  function parseHelper(fnBody) {
    try {
      return (new Function('return (' + fnBody + ')'))();
    } catch (error) {
      console.error('[menuUtils] helper parse failed:', error);
      return function() { return null; };
    }
  }

  function loadLegacyIfNeeded(autoLoadLegacy) {
    if (!autoLoadLegacy || typeof window.MenuUtils === 'function') return Promise.resolve();
    return new Promise((resolve, reject) => {
      const script = document.createElement('script');
      script.src = basePath + '/menuUtils.legacy.js';
      script.onload = resolve;
      script.onerror = reject;
      document.head.appendChild(script);
    });
  }

  fetch(basePath + '/menuUtilsData.json')
    .then(function(res) { return res.json(); })
    .then(function(payload) {
      const helperFns = {};
      Object.entries(payload.helpers || {}).forEach(function(entry) {
        helperFns[entry[0]] = parseHelper(entry[1]);
      });

      window.EVOLV.nodes[nodeName].utils.helpers = helperFns;
      return loadLegacyIfNeeded(payload.options && payload.options.autoLoadLegacy);
    })
    .then(function() {
      if (typeof window.MenuUtils === 'function' && !window.EVOLV.nodes[nodeName].utils.menuUtils) {
        window.EVOLV.nodes[nodeName].utils.menuUtils = new window.MenuUtils();
      }
    })
    .catch(function(error) {
      console.error('[menuUtils] bootstrap failed for ' + nodeName, error);
    });
})();
`;
  }

  /**
   * Generates the browser-side JavaScript that redefines MenuUtils and helper fns
   * @param {string} nodeName
   * @param {object} customHelpers  map of name: functionString pairs
   * @returns {string}  a JS snippet to run in the browser
   */
  generateLegacyMenuUtilsCode(nodeName, customHelpers = {}) {
    // Default helper implementations to expose alongside MenuUtils
    const defaultHelpers = {
      validateRequired: `function(value) {
        return value != null && value.toString().trim() !== '';
      }`,
      formatDisplayValue: `function(value, unit) {
        return \`${'${'}value} ${'${'}unit || ''}\`.trim();
      }`,
      validateScaling: `function(min, max) {
        return !isNaN(min) && !isNaN(max) && Number(min) < Number(max);
      }`,
      validateUnit: `function(unit) {
        return typeof unit === 'string' && unit.trim() !== '';
      }`,
    };

    // Merge any custom overrides
    const allHelpers = { ...defaultHelpers, ...customHelpers };

    // Build the helpers code block
    const helpersCode = Object.entries(allHelpers)
      .map(([name, fnBody]) => `        ${name}: ${fnBody}`)
      .join(',\n');

    // Introspect MenuUtils prototype to extract method definitions
    const proto = this.MenuUtils.prototype;
    const browserMethods = Object.getOwnPropertyNames(proto)
      .filter(key => key !== 'constructor')
      .map(methodName => {
        const fn = proto[methodName];
        const src = fn.toString();
        const isAsync = fn.constructor.name === 'AsyncFunction';
        // extract signature and body
        const signature = src.slice(src.indexOf('('));
        const prefix = isAsync ? 'async ' : '';
        return `  ${prefix}${methodName}${signature}`;
      })
      .join('\n\n');

    // Return a complete JS snippet for the browser
    return `
// Auto-generated MenuUtils for node: ${nodeName}
window.EVOLV = window.EVOLV || {};
window.EVOLV.nodes = window.EVOLV.nodes || {};
window.EVOLV.nodes.${nodeName} = window.EVOLV.nodes.${nodeName} || {};

class MenuUtils {
  constructor(opts) {
    // Allow same options API as server-side
    this.useCloud = opts.useCloud || false;
    this.projectSettings = opts.projectSettings || {};
    // any other client-side initialization...
  }

${browserMethods}
}

window.EVOLV.nodes.${nodeName}.utils = {
  menuUtils: new MenuUtils({}),
  helpers: {
${helpersCode}
  }
};

console.log('Loaded EVOLV.nodes.${nodeName}.utils.menuUtils');
`;
  }

  // Backward-compatible alias.
  generateMenuUtilsCode(nodeName, customHelpers = {}) {
    return this.generateLegacyMenuUtilsCode(nodeName, customHelpers);
  }
}

module.exports = EndpointUtils;
