document.addEventListener('DOMContentLoaded', () => {
  const patternsButton = document.getElementById('patterns-btn');
  const patternsModal = document.getElementById('patterns-modal');
  const patternsCloseButton = document.getElementById('patterns-close-btn');
  const patternsList = document.getElementById('patterns-list');
  const patternAddButton = document.getElementById('pattern-add-btn');
  const patternSendAllButton = document.getElementById('pattern-send-all-btn');
  const patternEditorModal = document.getElementById('pattern-editor-modal');
  const patternEditorCloseButton = document.getElementById('pattern-editor-close-btn');
  const patternCreateBtn = document.getElementById('pattern-create-btn');
  const patternCreateName = document.getElementById('pattern-create-name');
  const patternCreateMinDelay = document.getElementById('pattern-create-min-delay');
  const patternCreateMaxDelay = document.getElementById('pattern-create-max-delay');
  const patternCreateMaxColors = document.getElementById('pattern-create-max-colors');
  const patternCreateFile = document.getElementById('pattern-create-file');
  const patternCreateCode = document.getElementById('pattern-create-code');
  const patternCreateOverwrite = document.getElementById('pattern-create-overwrite');
  const patternCreateN = [1, 2, 3, 4, 5, 6, 7, 8].map((i) =>
    document.getElementById(`pattern-create-n${i}`),
  );
  const patternCreateNSection = document.getElementById('pattern-create-n-section');
  const patternCreateNEmpty = document.getElementById('pattern-create-n-empty');

  if (!patternsButton || !patternsModal || !patternsList) {
    return;
  }

  const coercePresetInt = (v, def = 0) => {
    if (typeof v === 'number' && Number.isFinite(v)) {
      return v;
    }
    const t = parseInt(String(v), 10);
    return Number.isFinite(t) ? t : def;
  };

  const coercePresetAuto = (preset) => {
    if (!preset || typeof preset !== 'object') {
      return true;
    }
    const v =
      preset.auto !== undefined && preset.auto !== null ? preset.auto : preset.a;
    if (typeof v === 'boolean') {
      return v;
    }
    if (v === 0 || v === '0') {
      return false;
    }
    if (v === 1 || v === '1') {
      return true;
    }
    if (typeof v === 'string') {
      const l = v.trim().toLowerCase();
      if (['false', '0', 'no', 'off'].includes(l)) {
        return false;
      }
      if (['true', '1', 'yes', 'on'].includes(l)) {
        return true;
      }
    }
    return true;
  };

  const getCurrentProfileId = async () => {
    try {
      const response = await fetch('/profiles/current', { headers: { Accept: 'application/json' } });
      if (!response.ok) {
        return null;
      }
      const data = await response.json();
      return data && (data.id || (data.profile && data.profile.id)) ? String(data.id || data.profile.id) : null;
    } catch (_) {
      return null;
    }
  };

  const filterPresetsForCurrentProfile = async (presetsObj) => {
    const scoped = presetsObj && typeof presetsObj === 'object' ? presetsObj : {};
    const currentProfileId = await getCurrentProfileId();
    if (!currentProfileId) {
      return scoped;
    }
    return Object.fromEntries(
      Object.entries(scoped).filter(([, preset]) => {
        if (!preset || typeof preset !== 'object') return false;
        if (!('profile_id' in preset)) return true;
        return String(preset.profile_id) === String(currentProfileId);
      }),
    );
  };

  const tabDeviceNamesFromSection = (section) => {
    if (typeof window.parseTabDeviceNames === 'function') {
      return window.parseTabDeviceNames(section);
    }
    const namesAttr = section && section.getAttribute('data-device-names');
    return namesAttr
      ? namesAttr.split(',').map((n) => n.trim()).filter((n) => n.length > 0)
      : [];
  };

  const postDriverSequence = async (sequence, targetMacs, delayS = 0.05) => {
    const body = {
      sequence,
      targets: Array.isArray(targetMacs) && targetMacs.length ? [...new Set(targetMacs)] : undefined,
      delay_s: delayS,
    };
    const res = await fetch('/presets/push', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
      credentials: 'same-origin',
      body: JSON.stringify(body),
    });
    if (!res.ok) {
      const err = await res.json().catch(() => ({}));
      throw new Error((err && err.error) || res.statusText || 'Send failed');
    }
    return res.json().catch(() => ({}));
  };

  const nReadableStringFromMeta = (meta, key) => {
    if (!meta || typeof meta !== 'object') {
      return '';
    }
    const pm = meta.parameter_mappings;
    if (pm && typeof pm === 'object' && typeof pm[key] === 'string') {
      const s = pm[key].trim();
      if (s) {
        return s;
      }
    }
    if (typeof meta[key] === 'string') {
      return meta[key].trim();
    }
    return '';
  };

  const setPatternEditorNFields = (mode, data) => {
    const meta = data && typeof data === 'object' ? data : {};
    let visible = 0;
    const grid = patternCreateNSection && patternCreateNSection.querySelector('.n-params-grid');
    const h3 = patternCreateNSection && patternCreateNSection.querySelector('h3');

    for (let i = 1; i <= 8; i += 1) {
      const key = `n${i}`;
      const labelEl = document.querySelector(`label[for="pattern-create-${key}"]`);
      const inputEl = document.getElementById(`pattern-create-${key}`);
      const groupEl = labelEl ? labelEl.closest('.n-param-group') : null;

      if (mode === 'create') {
        if (labelEl) {
          labelEl.textContent = `${key}:`;
          labelEl.style.display = '';
        }
        if (inputEl) {
          inputEl.value = '';
          inputEl.placeholder = 'Readable name (optional)';
          inputEl.removeAttribute('aria-label');
        }
        if (groupEl) {
          groupEl.style.display = '';
        }
        continue;
      }

      const readable = nReadableStringFromMeta(meta, key);
      const show = Boolean(readable);
      if (labelEl) {
        labelEl.textContent = '';
        labelEl.style.display = 'none';
      }
      if (inputEl) {
        inputEl.value = show ? readable : '';
        inputEl.placeholder = '';
        if (show) {
          inputEl.setAttribute('aria-label', readable);
        } else {
          inputEl.removeAttribute('aria-label');
          inputEl.value = '';
        }
      }
      if (groupEl) {
        groupEl.style.display = show ? '' : 'none';
      }
      if (show) {
        visible += 1;
      }
    }

    if (mode === 'create') {
      if (patternCreateNEmpty) {
        patternCreateNEmpty.style.display = 'none';
      }
      if (grid) {
        grid.style.display = '';
      }
      if (h3) {
        h3.style.display = '';
      }
      if (patternCreateNSection) {
        patternCreateNSection.style.display = '';
      }
      return;
    }

    if (patternCreateNEmpty) {
      patternCreateNEmpty.style.display = visible === 0 ? '' : 'none';
    }
    if (grid) {
      grid.style.display = visible === 0 ? 'none' : '';
    }
    if (h3) {
      h3.style.display = visible === 0 ? 'none' : '';
    }
  };

  const readFileAsText = (file) =>
    new Promise((resolve, reject) => {
      const reader = new FileReader();
      reader.onload = () => resolve(String(reader.result || ''));
      reader.onerror = () => reject(reader.error || new Error('read failed'));
      reader.readAsText(file);
    });

  const collectCreatePayload = async () => {
    const name = patternCreateName ? patternCreateName.value.trim() : '';
    if (!name) {
      throw new Error('Pattern name is required.');
    }
    let code = '';
    const fileInput = patternCreateFile && patternCreateFile.files && patternCreateFile.files[0];
    if (fileInput) {
      code = await readFileAsText(fileInput);
    } else if (patternCreateCode && patternCreateCode.value.trim()) {
      code = patternCreateCode.value;
    }
    if (!code.trim()) {
      throw new Error('Choose a .py file or paste source code.');
    }

    const payload = {
      name,
      code,
      min_delay: parseInt(patternCreateMinDelay && patternCreateMinDelay.value, 10) || 0,
      max_delay: parseInt(patternCreateMaxDelay && patternCreateMaxDelay.value, 10) || 0,
      max_colors: parseInt(patternCreateMaxColors && patternCreateMaxColors.value, 10) || 0,
      overwrite: !!(patternCreateOverwrite && patternCreateOverwrite.checked),
    };

    patternCreateN.forEach((el, idx) => {
      const key = `n${idx + 1}`;
      if (el && el.value.trim()) {
        payload[key] = el.value.trim();
      }
    });

    return payload;
  };

  const resetCreateForm = () => {
    if (patternCreateName) patternCreateName.value = '';
    if (patternCreateFile) patternCreateFile.value = '';
    if (patternCreateCode) patternCreateCode.value = '';
    if (patternCreateMinDelay) patternCreateMinDelay.value = '10';
    if (patternCreateMaxDelay) patternCreateMaxDelay.value = '10000';
    if (patternCreateMaxColors) patternCreateMaxColors.value = '10';
    patternCreateN.forEach((el) => {
      if (el) el.value = '';
    });
    if (patternCreateOverwrite) patternCreateOverwrite.checked = true;
    setPatternEditorNFields('create', {});
  };

  if (patternCreateBtn) {
    patternCreateBtn.addEventListener('click', async () => {
      try {
        const payload = await collectCreatePayload();
        const response = await fetch('/patterns/driver', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
          body: JSON.stringify(payload),
        });
        const data = await response.json().catch(() => ({}));
        if (!response.ok) {
          throw new Error((data && data.error) || 'Create failed');
        }
        alert(data.message || 'Pattern created.');
        resetCreateForm();
        if (patternEditorModal) {
          patternEditorModal.classList.remove('active');
        }
        await loadPatterns();
      } catch (e) {
        console.error('Create pattern failed:', e);
        alert(e.message || 'Failed to create pattern.');
      }
    });
  }

  /** on/off are implemented in driver firmware (presets.py), not as OTA ``.py`` files. */
  const FIRMWARE_BUILTIN_PATTERNS = new Set(['on', 'off']);

  const isFirmwareBuiltinPattern = (patternName) => {
    const id = String(patternName || '')
      .trim()
      .replace(/\.py$/i, '')
      .toLowerCase();
    return FIRMWARE_BUILTIN_PATTERNS.has(id);
  };

  const sendPatternToDevices = async (patternName) => {
    const response = await fetch(`/patterns/${encodeURIComponent(patternName)}/send`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
      body: JSON.stringify({}),
    });
    const data = await response.json().catch(() => ({}));
    if (!response.ok) {
      throw new Error((data && data.error) || 'Failed to send pattern');
    }
    const sentCount = data && typeof data.sent_count === 'number' ? data.sent_count : null;
    if (sentCount === null) {
      alert(`Sent "${patternName}" to devices.`);
    } else {
      alert(`Sent "${patternName}" to ${sentCount} device(s).`);
    }
  };

  const loadPatternMetadata = async (patternName, fallbackData) => {
    const raw = String(patternName || '').trim();
    const norm = raw.endsWith('.py') ? raw.slice(0, -3).trim() : raw;
    try {
      const response = await fetch('/patterns/definitions', {
        cache: 'no-store',
        headers: { Accept: 'application/json' },
      });
      if (!response.ok) {
        throw new Error('Failed to load pattern definitions');
      }
      const definitions = await response.json();
      if (definitions && typeof definitions === 'object') {
        if (definitions[raw]) {
          return definitions[raw];
        }
        if (norm && definitions[norm]) {
          return definitions[norm];
        }
        if (norm) {
          const lower = norm.toLowerCase();
          const matched = Object.keys(definitions).find(
            (k) => String(k).toLowerCase() === lower,
          );
          if (matched) {
            return definitions[matched];
          }
        }
      }
    } catch (error) {
      console.error('Load pattern definitions failed:', error);
    }
    return fallbackData || {};
  };

  const loadPatternIntoEditor = async (patternName, fallbackData) => {
    const data = await loadPatternMetadata(patternName, fallbackData);
    if (patternCreateName) {
      patternCreateName.value = patternName;
    }
    if (patternCreateMinDelay) {
      patternCreateMinDelay.value =
        data && data.min_delay !== undefined ? String(data.min_delay) : '10';
    }
    if (patternCreateMaxDelay) {
      patternCreateMaxDelay.value =
        data && data.max_delay !== undefined ? String(data.max_delay) : '10000';
    }
    if (patternCreateMaxColors) {
      patternCreateMaxColors.value =
        data && data.max_colors !== undefined ? String(data.max_colors) : '10';
    }
    setPatternEditorNFields('edit', data);
    if (patternCreateOverwrite) {
      patternCreateOverwrite.checked = true;
    }
    if (patternCreateFile) {
      patternCreateFile.value = '';
    }

    try {
      const raw = String(patternName || '').trim();
      const fileSegment = /\.py$/i.test(raw) ? raw : `${raw}.py`;
      const response = await fetch(`/patterns/ota/file/${encodeURIComponent(fileSegment)}`, {
        headers: { Accept: 'text/plain' },
      });
      if (!response.ok) {
        throw new Error('Failed to load pattern file');
      }
      const source = await response.text();
      if (patternCreateCode) {
        patternCreateCode.value = source || '';
        patternCreateCode.focus();
      }
    } catch (error) {
      console.error('Load pattern source failed:', error);
      alert('Could not load pattern source into editor.');
    }
  };

  const renderPatterns = (patterns) => {
    patternsList.innerHTML = '';
    const entries = Object.entries(patterns || {});
    if (!entries.length) {
      const empty = document.createElement('p');
      empty.className = 'muted-text';
      empty.textContent = 'No patterns found.';
      patternsList.appendChild(empty);
      return;
    }
    entries.forEach(([patternName, data]) => {
      const row = document.createElement('div');
      row.className = 'profiles-row';

      const label = document.createElement('span');
      label.textContent = patternName;

      row.appendChild(label);

      if (isFirmwareBuiltinPattern(patternName)) {
        const note = document.createElement('span');
        note.className = 'muted-text';
        note.style.fontSize = '0.85em';
        note.textContent = 'Built-in (no OTA module)';
        row.appendChild(note);
      } else {
        const sendBtn = document.createElement('button');
        sendBtn.className = 'btn btn-primary btn-small';
        sendBtn.textContent = 'Send';
        sendBtn.addEventListener('click', async () => {
          try {
            await sendPatternToDevices(patternName);
          } catch (error) {
            console.error('Send pattern failed:', error);
            alert(error.message || 'Failed to send pattern.');
          }
        });

        const editBtn = document.createElement('button');
        editBtn.className = 'btn btn-secondary btn-small';
        editBtn.textContent = 'Edit';
        editBtn.addEventListener('click', async () => {
          if (patternEditorModal) {
            patternEditorModal.classList.add('active');
          }
          await loadPatternIntoEditor(patternName, data || {});
        });

        row.appendChild(editBtn);
        row.appendChild(sendBtn);
      }

      patternsList.appendChild(row);
    });
  };

  async function loadPatterns() {
    patternsList.innerHTML = '';
    const loading = document.createElement('p');
    loading.className = 'muted-text';
    loading.textContent = 'Loading patterns...';
    patternsList.appendChild(loading);

    try {
      const response = await fetch('/patterns', {
        cache: 'no-store',
        headers: { Accept: 'application/json' },
      });
      if (!response.ok) {
        throw new Error('Failed to load patterns');
      }
      const patterns = await response.json();
      renderPatterns(patterns);
    } catch (error) {
      console.error('Load patterns failed:', error);
      patternsList.innerHTML = '';
      const errorMessage = document.createElement('p');
      errorMessage.className = 'muted-text';
      errorMessage.textContent = 'Failed to load patterns.';
      patternsList.appendChild(errorMessage);
    }
  }

  const openModal = () => {
    patternsModal.classList.add('active');
    loadPatterns();
  };

  const closeModal = () => {
    patternsModal.classList.remove('active');
  };

  patternsButton.addEventListener('click', openModal);
  if (patternAddButton) {
    patternAddButton.addEventListener('click', () => {
      resetCreateForm();
      if (patternEditorModal) {
        patternEditorModal.classList.add('active');
      }
    });
  }
  if (patternEditorCloseButton) {
    patternEditorCloseButton.addEventListener('click', () => {
      if (patternEditorModal) {
        patternEditorModal.classList.remove('active');
      }
    });
  }
  if (patternsCloseButton) {
    patternsCloseButton.addEventListener('click', closeModal);
  }

  if (patternSendAllButton) {
    patternSendAllButton.addEventListener('click', async () => {
      const section = document.querySelector('.presets-section[data-zone-id]');
      const zoneId = section ? section.dataset.zoneId : null;
      if (!zoneId) {
        alert('Could not determine current zone.');
        return;
      }
      const deviceNames = tabDeviceNamesFromSection(section);
      if (!deviceNames.length) {
        alert('No devices found in the current zone.');
        return;
      }
      try {
        const [zoneRes, presetsRes] = await Promise.all([
          fetch(`/zones/${zoneId}`, { headers: { Accept: 'application/json' } }),
          fetch('/presets', { headers: { Accept: 'application/json' } }),
        ]);
        if (!zoneRes.ok || !presetsRes.ok) {
          throw new Error('Failed to load zone presets');
        }
        const zoneData = await zoneRes.json();
        const allPresetsRaw = await presetsRes.json();
        const allPresets = await filterPresetsForCurrentProfile(allPresetsRaw);
        const zonePresetIds = Array.isArray(zoneData.presets_flat)
          ? zoneData.presets_flat.map((id) => String(id))
          : [];
        if (!zonePresetIds.length) {
          alert('No presets found in this zone.');
          return;
        }

        const wirePresets = {};
        zonePresetIds.forEach((presetId) => {
          const preset = allPresets[presetId];
          if (!preset) {
            return;
          }
          const colors = Array.isArray(preset.colors) && preset.colors.length
            ? preset.colors
            : ['#FFFFFF'];
          const presetAuto = coercePresetAuto(preset);
          wirePresets[presetId] = {
            pattern: preset.pattern || 'off',
            colors,
            delay: typeof preset.delay === 'number' ? preset.delay : 100,
            brightness: typeof preset.brightness === 'number'
              ? preset.brightness
              : (typeof preset.br === 'number' ? preset.br : 127),
            auto: presetAuto,
            a: presetAuto,
            n1: coercePresetInt(preset.n1),
            n2: coercePresetInt(preset.n2),
            n3: coercePresetInt(preset.n3),
            n4: coercePresetInt(preset.n4),
            n5: coercePresetInt(preset.n5),
            n6: coercePresetInt(preset.n6),
          };
        });
        if (!Object.keys(wirePresets).length) {
          alert('No matching presets found to send.');
          return;
        }

        const select = {};
        deviceNames.forEach((name) => {
          if (name) {
            select[name] = zonePresetIds.slice();
          }
        });
        const targetMacs =
          typeof window.tabsManager !== 'undefined' &&
          typeof window.tabsManager.resolveTabDeviceMacs === 'function'
            ? await window.tabsManager.resolveTabDeviceMacs(deviceNames)
            : [];

        const sequence = [
          { v: '1', clear_presets: true, save: true },
          { v: '1', presets: wirePresets, save: true },
        ];
        if (Object.keys(select).length) {
          sequence.push({ v: '1', select });
        }
        await postDriverSequence(sequence, targetMacs, 0.05);
      } catch (error) {
        console.error('Send all patterns failed:', error);
        alert('Failed to send all patterns.');
      }
    });
  }

});
