const { assetCategoryManager } = require('../../datasets/assetData');
const assetApiConfig = require('../configs/assetApiConfig.js');

class AssetMenu {
  constructor({ manager = assetCategoryManager, softwareType = null } = {}) {
    this.manager = manager;
    this.softwareType = softwareType;
    this.categories = this.manager
      .listCategories({ withMeta: true })
      .reduce((map, meta) => {
        map[meta.softwareType] = this.manager.getCategory(meta.softwareType);
        return map;
      }, {});
  }

  normalizeCategory(key) {
    const category = this.categories[key];
    if (!category) {
      return null;
    }

    return {
      ...category,
      label: category.label || category.softwareType || key,
      suppliers: (category.suppliers || []).map((supplier) => ({
        ...supplier,
        id: supplier.id || supplier.name,
        types: (supplier.types || []).map((type) => ({
          ...type,
          id: type.id || type.name,
          models: (type.models || []).map((model) => ({
            ...model,
            id: model.id || model.name,
            units: model.units || []
          }))
        }))
      }))
    };
  }

  resolveCategoryForNode(nodeName) {
    const keys = Object.keys(this.categories);
    if (keys.length === 0) {
      return null;
    }

    if (this.softwareType && this.categories[this.softwareType]) {
      return this.softwareType;
    }

    if (nodeName) {
      const normalized = typeof nodeName === 'string' ? nodeName.toLowerCase() : nodeName;
      if (normalized && this.categories[normalized]) {
        return normalized;
      }
    }

    return keys[0];
  }

  getAllMenuData(nodeName) {
    const categoryKey = this.resolveCategoryForNode(nodeName);
    const selectedCategories = {};

    if (categoryKey && this.categories[categoryKey]) {
      selectedCategories[categoryKey] = this.normalizeCategory(categoryKey);
    }

    return {
      categories: selectedCategories,
      defaultCategory: categoryKey,
      apiConfig: {
        url: `${assetApiConfig.baseUrl}/apis/products/PLC/integration/`,
        headers: { ...assetApiConfig.headers }
      }
    };
  }

  getClientInitCode(nodeName) {
    const htmlCode = this.getHtmlInjectionCode(nodeName);
    const dataCode = this.getDataInjectionCode(nodeName);
    const eventsCode = this.getEventInjectionCode(nodeName);
    const syncCode = this.getSyncInjectionCode(nodeName);
    const saveCode = this.getSaveInjectionCode(nodeName);

    return `
      // --- AssetMenu for ${nodeName} ---
      window.EVOLV.nodes.${nodeName}.assetMenu =
        window.EVOLV.nodes.${nodeName}.assetMenu || {};

      ${htmlCode}
      ${dataCode}
      ${eventsCode}
      ${syncCode}
      ${saveCode}

      window.EVOLV.nodes.${nodeName}.assetMenu.initEditor = function(node) {
        console.log('Initializing asset properties for ${nodeName}');
        this.injectHtml();
        this.wireEvents(node);
        this.loadData(node).catch((error) =>
          console.error('Asset menu load failed:', error)
        );
      };
    `;
  }

  getDataInjectionCode(nodeName) {
    return `
      // Asset data loader for ${nodeName}
      window.EVOLV.nodes.${nodeName}.assetMenu.loadData = async function(node) {
        const menuAsset = window.EVOLV.nodes.${nodeName}.menuData.asset || {};
        const categories = menuAsset.categories || {};
        const defaultCategory = menuAsset.defaultCategory || Object.keys(categories)[0] || null;
        const apiConfig = menuAsset.apiConfig || {};
        const elems = {
          supplier: document.getElementById('node-input-supplier'),
          type: document.getElementById('node-input-assetType'),
          model: document.getElementById('node-input-model'),
          unit: document.getElementById('node-input-unit')
        };

        function resolveCategoryKey() {
          if (node.softwareType && categories[node.softwareType]) {
            return node.softwareType;
          }
          if (node.category && categories[node.category]) {
            return node.category;
          }
          return defaultCategory;
        }

        function normalizeModel(model = {}) {
          return {
            id: model.id ?? model.name,
            name: model.name,
            units: model.units || []
          };
        }

        function normalizeType(type = {}) {
          return {
            id: type.id || type.name,
            name: type.name,
            models: Array.isArray(type.models)
              ? type.models.map(normalizeModel)
              : []
          };
        }

        function normalizeSupplier(supplier = {}) {
          const types = (supplier.categories || []).reduce((acc, category) => {
            const categoryTypes = Array.isArray(category.types)
              ? category.types.map(normalizeType)
              : [];
            return acc.concat(categoryTypes);
          }, []);
          return {
            id: supplier.id || supplier.name,
            name: supplier.name,
            types
          };
        }

        function normalizeApiCategory(key, label, suppliers = []) {
          const normalizedSuppliers = suppliers
            .map(normalizeSupplier)
            .filter((supplier) => supplier.types && supplier.types.length);
          if (!normalizedSuppliers.length) {
            return null;
          }
          return {
            softwareType: key,
            label: label || key,
            suppliers: normalizedSuppliers
          };
        }

        async function fetchCategoryFromApi(key) {
          if (!apiConfig.url || !key) {
            return null;
          }
          const response = await fetch(apiConfig.url, {
            headers: apiConfig.headers || {}
          });
          if (!response.ok) {
            throw new Error('Asset API request failed: ' + response.status);
          }
          const payload = await response.json();
          if (!payload || payload.success === false || !Array.isArray(payload.data)) {
            throw new Error(payload?.message || 'Unexpected asset API response');
          }
          return normalizeApiCategory(key, node.softwareType || key, payload.data);
        }

        // Non-dispatching populate (matches the wireEvents version). The
        // load path below explicitly walks supplier -> type -> model ->
        // unit in order using saved node.* values, so auto-dispatched
        // change events (which previously cascaded through wireEvents'
        // listeners and double-populated everything) are no longer needed.
        function populate(selectEl, items = [], selectedValue, mapFn, placeholderText = 'Select...') {
          if (!selectEl) return;
          const mapper = typeof mapFn === 'function'
            ? mapFn
            : (value) => ({ value, label: value });

          selectEl.innerHTML = '';

          const placeholder = document.createElement('option');
          placeholder.value = '';
          placeholder.textContent = placeholderText;
          placeholder.disabled = true;
          placeholder.selected = true;
          selectEl.appendChild(placeholder);

          items.forEach((item) => {
            const option = mapper(item);
            if (!option || typeof option.value === 'undefined') {
              return;
            }
            const opt = document.createElement('option');
            opt.value = option.value;
            opt.textContent = option.label;
            selectEl.appendChild(opt);
          });

          if (selectedValue) {
            selectEl.value = selectedValue;
            if (!selectEl.value) {
              selectEl.value = '';
            }
          } else {
            selectEl.value = '';
          }
        }

        const categoryKey = resolveCategoryKey();
        const resolvedCategoryKey = categoryKey || defaultCategory;
        let activeCategory = resolvedCategoryKey ? categories[resolvedCategoryKey] : null;

        if (resolvedCategoryKey) {
          node.category = resolvedCategoryKey;
        }

        try {
          const apiCategory = await fetchCategoryFromApi(resolvedCategoryKey);
          if (apiCategory) {
            categories[resolvedCategoryKey] = apiCategory;
            activeCategory = apiCategory;
          }
        } catch (error) {
          console.warn('[AssetMenu] API lookup failed for ${nodeName}, using local asset data', error);
        }

        const suppliers = activeCategory ? activeCategory.suppliers : [];
        populate(
          elems.supplier,
          suppliers,
          node.supplier,
          (supplier) => ({ value: supplier.id || supplier.name, label: supplier.name }),
          suppliers.length ? 'Select...' : 'No suppliers available'
        );

        const activeSupplier = suppliers.find(
          (supplier) => String(supplier.id || supplier.name) === String(node.supplier)
        );
        const types = activeSupplier ? activeSupplier.types : [];
        populate(
          elems.type,
          types,
          node.assetType,
          (type) => ({ value: type.id || type.name, label: type.name }),
          activeSupplier ? 'Select...' : 'Awaiting Supplier Selection'
        );

        const activeType = types.find(
          (type) => String(type.id || type.name) === String(node.assetType)
        );
        const models = activeType ? activeType.models : [];
        populate(
          elems.model,
          models,
          node.model,
          (model) => ({ value: model.id || model.name, label: model.name }),
          activeType ? 'Select...' : 'Awaiting Type Selection'
        );

        const activeModel = models.find(
          (model) => String(model.id || model.name) === String(node.model)
        );
        if (activeModel) {
          node.modelMetadata = activeModel;
          node.modelName = activeModel.name;
        }
        populate(
          elems.unit,
          activeModel ? activeModel.units || [] : [],
          node.unit,
          (unit) => ({ value: unit, label: unit }),
          activeModel ? 'Select...' : activeType ? 'Awaiting Model Selection' : 'Awaiting Type Selection'
        );
        this.setAssetTagNumber(node, node.assetTagNumber || '');
      };
    `;
  }

  getEventInjectionCode(nodeName) {
    return `
      // Asset event wiring for ${nodeName}
      //
      // The supplier -> type -> model -> unit chain is a strict downward
      // cascade: each select rebuilds the next based on the currently
      // selected value above it. Two earlier bugs in this code:
      //
      //   1. populate() auto-dispatched a synthetic 'change' event whenever
      //      the value of the rebuilt select differed from before the
      //      rebuild. That triggered the *child* select's listener mid-way
      //      through the *parent* listener, which then continued and
      //      blindly overwrote the child select with empty content. Net
      //      effect: model dropdown showed 'Awaiting Type Selection' even
      //      though a type was clearly selected.
      //
      //   2. Each downstream wipe ran unconditionally inside the parent
      //      handler, instead of being driven by the actual current value
      //      of the child select.
      //
      // Fix: populate() no longer dispatches change. Cascade is explicit
      // via cascadeFromSupplier() / cascadeFromType() / cascadeFromModel()
      // which are called from each handler. The same helpers run on
      // initial load so behaviour is identical whether the user picked the
      // value or it came from a saved node.
      window.EVOLV.nodes.${nodeName}.assetMenu.wireEvents = function(node) {
        const menuAsset = window.EVOLV.nodes.${nodeName}.menuData.asset || {};
        const categories = menuAsset.categories || {};
        const defaultCategory = menuAsset.defaultCategory || Object.keys(categories)[0] || null;
        const elems = {
          supplier: document.getElementById('node-input-supplier'),
          type: document.getElementById('node-input-assetType'),
          model: document.getElementById('node-input-model'),
          unit: document.getElementById('node-input-unit')
        };

        // populate(): rebuild a <select> with a placeholder + items.
        // No change-event dispatch — cascading is done explicitly by the
        // caller via cascadeFrom*() so the order of operations is
        // predictable.
        function populate(selectEl, items, selectedValue, mapFn, placeholderText) {
          if (!selectEl) return;
          if (!Array.isArray(items)) items = [];
          if (!placeholderText) placeholderText = 'Select...';
          const mapper = typeof mapFn === 'function'
            ? mapFn
            : (value) => ({ value: value, label: value });

          selectEl.innerHTML = '';

          const placeholder = document.createElement('option');
          placeholder.value = '';
          placeholder.textContent = placeholderText;
          placeholder.disabled = true;
          placeholder.selected = true;
          selectEl.appendChild(placeholder);

          items.forEach(function (item) {
            const option = mapper(item);
            if (!option || typeof option.value === 'undefined') return;
            const opt = document.createElement('option');
            opt.value = option.value;
            opt.textContent = option.label;
            selectEl.appendChild(opt);
          });

          if (selectedValue) {
            selectEl.value = selectedValue;
            if (!selectEl.value) selectEl.value = '';
          } else {
            selectEl.value = '';
          }
        }

        function resolveCategoryKey() {
          if (node.softwareType && categories[node.softwareType]) return node.softwareType;
          if (node.category && categories[node.category]) return node.category;
          return defaultCategory;
        }
        function getActiveCategory() {
          const key = resolveCategoryKey();
          return key ? categories[key] : null;
        }

        node.category = resolveCategoryKey();

        // Lookup helpers — read from the *currently selected* values in the
        // DOM, not from node.* (which may not yet be in sync).
        function findSupplier() {
          const cat = getActiveCategory();
          if (!cat || !Array.isArray(cat.suppliers)) return null;
          const id = String(elems.supplier.value);
          return cat.suppliers.find(function (s) {
            return String(s.id || s.name) === id;
          }) || null;
        }
        function findType(supplier) {
          if (!supplier || !Array.isArray(supplier.types)) return null;
          const id = String(elems.type.value);
          return supplier.types.find(function (t) {
            return String(t.id || t.name) === id;
          }) || null;
        }
        function findModel(type) {
          if (!type || !Array.isArray(type.models)) return null;
          const id = String(elems.model.value);
          return type.models.find(function (m) {
            return String(m.id || m.name) === id;
          }) || null;
        }

        // === Cascade rebuild functions ==========================
        // Each one rebuilds the dropdown for the *level it owns* plus all
        // levels below it, using the current values in the DOM. Called by
        // the corresponding change handler AND by initial load so both
        // paths produce identical state.

        function cascadeFromSupplier() {
          const supplier = findSupplier();
          const types = supplier ? supplier.types : [];
          populate(
            elems.type,
            types,
            node.assetType,
            function (t) { return { value: t.id || t.name, label: t.name }; },
            supplier ? 'Select...' : 'Awaiting Supplier Selection'
          );
          // After repopulating type, propagate down. cascadeFromType()
          // will read the new elems.type.value (which was set by populate
          // to either the saved node.assetType or '') and rebuild model.
          cascadeFromType();
        }

        function cascadeFromType() {
          const supplier = findSupplier();
          const type = findType(supplier);
          const models = type ? type.models : [];
          populate(
            elems.model,
            models,
            node.model,
            function (m) { return { value: m.id || m.name, label: m.name }; },
            type ? 'Select...' : 'Awaiting Type Selection'
          );
          node.modelMetadata = null;
          cascadeFromModel();
        }

        function cascadeFromModel() {
          const supplier = findSupplier();
          const type = findType(supplier);
          const model = findModel(type);
          node.modelMetadata = model;
          node.modelName = model ? model.name : '';
          populate(
            elems.unit,
            model ? (model.units || []) : [],
            node.unit,
            function (u) { return { value: u, label: u }; },
            model ? 'Select...' : (type ? 'Awaiting Model Selection' : 'Awaiting Type Selection')
          );
        }

        elems.supplier.addEventListener('change', cascadeFromSupplier);
        elems.type.addEventListener('change', cascadeFromType);
        elems.model.addEventListener('change', cascadeFromModel);

        // Expose the cascades so loadData() (or future code) can re-run
        // them after async data arrives without duplicating logic.
        window.EVOLV.nodes.${nodeName}.assetMenu._cascade = {
          fromSupplier: cascadeFromSupplier,
          fromType: cascadeFromType,
          fromModel: cascadeFromModel,
        };
      };
    `;
  }

  getSyncInjectionCode(nodeName) {
    return `
      // Asset synchronization helpers for ${nodeName}
      window.EVOLV.nodes.${nodeName}.assetMenu.setAssetTagNumber = function(node, tag) {
        const normalized = tag ? tag.toString() : '';
        const input = document.getElementById('node-input-assetTagNumber');
        const hint = document.getElementById('node-input-assetTagNumber-hint');
        console.info('[AssetMenu] tag number update', {
          nodeId: node && node.id ? node.id : null,
          tag: normalized
        });
        if (input) {
          input.value = normalized;
        }
        if (hint) {
          hint.textContent = normalized ? 'Assigned tag ' + normalized : 'Not registered yet';
        }
        if (node) {
          node.assetTagNumber = normalized;
        }
      };

      window.EVOLV.nodes.${nodeName}.assetMenu.buildSyncRequest = function(node) {
        const tagInput = document.getElementById('node-input-assetTagNumber');
        const candidateTag = tagInput && tagInput.value ? tagInput.value.trim() : '';
        const fallbackTag = node && node.assetTagNumber ? node.assetTagNumber : '';
        const registrationDefaults =
          (window.EVOLV.nodes.${nodeName}.config && window.EVOLV.nodes.${nodeName}.config.assetRegistration && window.EVOLV.nodes.${nodeName}.config.assetRegistration.default) || {};
        const displayName = node && node.name ? node.name : node && node.id ? node.id : '${nodeName}';
        console.info('[AssetMenu] build sync payload', {
          nodeId: node && node.id ? node.id : null,
          candidateTag,
          fallbackTag,
          status: registrationDefaults.status || 'actief'
        });
        return {
          asset: {
            tagNumber: candidateTag || fallbackTag,
            supplier: node && node.supplier ? node.supplier : '',
            assetType: node && node.assetType ? node.assetType : '',
            model: node && node.model ? node.model : '',
            unit: node && node.unit ? node.unit : '',
            assetName: displayName,
            assetDescription: displayName,
            assetStatus: registrationDefaults.status || 'actief',
            modelMetadata: node && node.modelMetadata ? node.modelMetadata : null
          },
          nodeId: node && node.id ? node.id : null,
          nodeName: node && node.type ? node.type : '${nodeName}'
        };
      };

      window.EVOLV.nodes.${nodeName}.assetMenu.syncAsset = function(node) {
        const payload = this.buildSyncRequest(node);
        const redSettings = window.RED && window.RED.settings;
        const adminRoot = redSettings ? redSettings.httpAdminRoot : '';
        const trimmedRoot = adminRoot && adminRoot.endsWith('/') ? adminRoot.slice(0, -1) : adminRoot || '';
        const prefix = trimmedRoot || '';
        const endpoint = (prefix || '') + '/${nodeName}/asset-reg';
        console.info('[AssetMenu] sync request', {
          endpoint,
          nodeId: node && node.id ? node.id : null,
          tagNumber: payload && payload.asset ? payload.asset.tagNumber : null
        });
        fetch(endpoint, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(payload)
        })
          .then((res) =>
            res.json().catch((err) => {
              console.warn('[AssetMenu] asset sync response is not JSON', err);
              return { success: false, message: err.message || 'Invalid API response' };
            })
          )
          .then((result) => {
            console.info('[AssetMenu] sync response', result);
            if (result && result.success) {
              const newTag = (result.data && result.data.asset_tag_number) || payload.asset.tagNumber || '';
              this.setAssetTagNumber(node, newTag);
              if (window.RED && typeof window.RED.notify === 'function') {
                window.RED.notify('Asset synced: ' + (newTag || 'no tag'), 'info');
              }
            } else {
              console.warn('[AssetMenu] asset sync failed', result && result.message);
            }
          })
          .catch((error) => {
            console.error('[AssetMenu] asset sync error', error);
          });
      };
    `;
  }

  getHtmlTemplate() {
    return `
      <!-- Asset Properties -->
      <hr />
      <h3>Asset selection</h3>
      <div class="form-row">
        <label for="node-input-supplier"><i class="fa fa-industry"></i> Supplier</label>
        <select id="node-input-supplier" style="width:70%;"></select>
      </div>
      <div class="form-row">
        <label for="node-input-assetType"><i class="fa fa-puzzle-piece"></i> Type</label>
        <select id="node-input-assetType" style="width:70%;"></select>
      </div>
      <div class="form-row">
        <label for="node-input-model"><i class="fa fa-wrench"></i> Model</label>
        <select id="node-input-model" style="width:70%;"></select>
      </div>
      <div class="form-row">
        <label for="node-input-unit"><i class="fa fa-balance-scale"></i> Unit</label>
        <select id="node-input-unit" style="width:70%;"></select>
      </div>
      <div class="form-row">
        <label for="node-input-assetTagNumber"><i class="fa fa-hashtag"></i> Asset Tag</label>
        <input type="text" id="node-input-assetTagNumber" readonly style="width:70%;" />
        <div class="form-tips" id="node-input-assetTagNumber-hint">Not registered yet</div>
      </div>
      <hr />
    `;
  }

  getHtmlInjectionCode(nodeName) {
    const htmlTemplate = this.getHtmlTemplate()
      .replace(/`/g, '\\`')
      .replace(/\$/g, '\\$');

    return `
      // Asset HTML injection for ${nodeName}
      window.EVOLV.nodes.${nodeName}.assetMenu.injectHtml = function() {
        const placeholder = document.getElementById('asset-fields-placeholder');
        if (placeholder && !placeholder.hasChildNodes()) {
          placeholder.innerHTML = \`${htmlTemplate}\`;
          console.log('Asset HTML injected successfully');
        }
      };
    `;
  }

  getSaveInjectionCode(nodeName) {
    return `
      // Asset save handler for ${nodeName}
      window.EVOLV.nodes.${nodeName}.assetMenu.saveEditor = function(node) {
        console.log('Saving asset properties for ${nodeName}');
        const menuAsset = window.EVOLV.nodes.${nodeName}.menuData.asset || {};
        const categories = menuAsset.categories || {};
        const defaultCategory = menuAsset.defaultCategory || Object.keys(categories)[0] || null;
        const resolveCategoryKey = () => {
          if (node.softwareType && categories[node.softwareType]) {
            return node.softwareType;
          }
          if (node.category && categories[node.category]) {
            return node.category;
          }
          return defaultCategory || '';
        };

        node.category = resolveCategoryKey();

        const fields = ['supplier', 'assetType', 'model', 'unit', 'assetTagNumber'];
        const errors = [];

        fields.forEach((field) => {
          const el = document.getElementById(\`node-input-\${field}\`);
          node[field] = el ? el.value : '';
        });

        if (node.assetType && !node.unit) {
          errors.push('Unit must be set when a type is specified.');
        }
        if (!node.unit) {
          errors.push('Unit is required.');
        }

        errors.forEach((msg) => RED.notify(msg, 'error'));

        const saved = fields.reduce((acc, field) => {
          acc[field] = node[field];
          return acc;
        }, {});
        if (node.modelMetadata && typeof node.modelMetadata.id !== 'undefined') {
          saved.modelId = node.modelMetadata.id;
        }
        console.log('[AssetMenu] save result:', saved);

        if (errors.length === 0 && this.syncAsset) {
          this.syncAsset(node);
        }

        return errors.length === 0;
      };
    `;
  }
}

module.exports = AssetMenu;
