import { ROBOT_TEMPLATE_LIST, getTemplate, defaultFieldsFor } from '../robot/robotComponents.js';
import { createEmptyRobotFile, parseRobotFileText } from '../robot/robotFile.js';
import { applyRobotToWorkspace } from '../robot/applyRobotToWorkspace.js';
import { importRobotFromWorkspace } from '../robot/robotImport.js';
import { getToken, onAuthChange } from '../auth/client.js';

/** @typedef {{ kind: string, fields: Record<string, string> }} RobotComponentRow */

/** @type {import('blockly').WorkspaceSvg | null} */
let workspace = null;
/** @type {() => string} */
let getDeviceId = () => 'esp32s3';

/** @type {{ version: number, device: string, components: RobotComponentRow[] }} */
let robotDocument = createEmptyRobotFile('esp32s3');

let selectedIndex = -1;

let listEl;
let fileListEl;
let fileNameInputEl;
let editorEl;
let statusEl;
let deviceNoteEl;
let addSelectEl;
let fileInputEl;
let panelEl;
let selectedFileEntry = null;

const BROWSER_STORAGE_KEY = 'esp32block_robot_files';
const CLOUD_PREFIX = 'robot::';

function setStatus(msg, isError = false) {
  if (!statusEl) return;
  statusEl.textContent = msg || '';
  statusEl.classList.toggle('robot-status-error', !!isError);
}

function updateDeviceNote() {
  if (!deviceNoteEl) return;
  const cur = getDeviceId();
  if (robotDocument.device === cur) {
    deviceNoteEl.textContent = `Target device: ${cur}`;
    deviceNoteEl.classList.remove('robot-note-warn');
  } else {
    deviceNoteEl.textContent = `File device: ${robotDocument.device} (editor: ${cur})`;
    deviceNoteEl.classList.add('robot-note-warn');
  }
}

function apiUrl(path) {
  const base = import.meta.env.BASE_URL || '/';
  const p = path.startsWith('/') ? path.slice(1) : path;
  return (base.endsWith('/') ? base : base + '/') + p;
}

function getBrowserFiles() {
  try {
    return JSON.parse(localStorage.getItem(BROWSER_STORAGE_KEY) || '{}');
  } catch {
    return {};
  }
}

function setBrowserFiles(files) {
  localStorage.setItem(BROWSER_STORAGE_KEY, JSON.stringify(files));
}

async function fetchCloudFiles() {
  const token = getToken();
  if (!token) return [];
  const res = await fetch(apiUrl('/api/projects'), {
    headers: { Authorization: `Bearer ${token}` },
  });
  if (!res.ok) return [];
  const data = await res.json();
  const projects = Array.isArray(data?.projects) ? data.projects : [];
  return projects
    .filter((p) => typeof p?.name === 'string' && p.name.startsWith(CLOUD_PREFIX))
    .map((p) => ({ name: p.name.slice(CLOUD_PREFIX.length), state: p.state }));
}

async function saveCloudFile(name, state) {
  const token = getToken();
  if (!token) return false;
  const res = await fetch(apiUrl('/api/projects'), {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${token}`,
    },
    body: JSON.stringify({ name: `${CLOUD_PREFIX}${name}`, state }),
  });
  return res.ok;
}

function toDisplayLabel(entry) {
  const tag = entry.source === 'cloud' ? '[cloud]' : '[browser]';
  return `${entry.name} ${tag}`;
}

async function refreshFileList() {
  if (!fileListEl) return;
  const browser = getBrowserFiles();
  const browserEntries = Object.keys(browser).sort((a, b) => a.localeCompare(b)).map((name) => ({
    source: 'browser',
    name,
    state: browser[name],
  }));
  let cloudEntries = [];
  if (getToken()) {
    cloudEntries = await fetchCloudFiles();
    cloudEntries = cloudEntries.map((f) => ({ source: 'cloud', name: f.name, state: f.state }));
  }

  const entries = [...browserEntries, ...cloudEntries].sort((a, b) => {
    const nameCmp = a.name.localeCompare(b.name);
    if (nameCmp !== 0) return nameCmp;
    return a.source.localeCompare(b.source);
  });

  fileListEl.innerHTML = '';
  selectedFileEntry = null;
  if (entries.length === 0) {
    fileListEl.innerHTML = '<li class="empty-msg">No saved robot files</li>';
    return;
  }
  for (const entry of entries) {
    const li = document.createElement('li');
    li.textContent = toDisplayLabel(entry);
    li.addEventListener('click', () => {
      fileListEl.querySelectorAll('li').forEach((el) => el.classList.remove('selected'));
      li.classList.add('selected');
      selectedFileEntry = entry;
      if (fileNameInputEl) fileNameInputEl.value = entry.name;
    });
    li.addEventListener('dblclick', () => {
      fileListEl.querySelectorAll('li').forEach((el) => el.classList.remove('selected'));
      li.classList.add('selected');
      selectedFileEntry = entry;
      if (fileNameInputEl) fileNameInputEl.value = entry.name;
      openSelectedFile();
    });
    fileListEl.appendChild(li);
  }
}

function renderAddSelect() {
  if (!addSelectEl) return;
  addSelectEl.innerHTML = '';
  const ph = document.createElement('option');
  ph.value = '';
  ph.textContent = 'Add component…';
  addSelectEl.appendChild(ph);
  for (const t of ROBOT_TEMPLATE_LIST) {
    const opt = document.createElement('option');
    opt.value = t.id;
    opt.textContent = t.label;
    addSelectEl.appendChild(opt);
  }
}

function renderList() {
  if (!listEl) return;
  listEl.innerHTML = '';
  robotDocument.components.forEach((row, idx) => {
    const tmpl = getTemplate(row.kind);
    const li = document.createElement('li');
    li.className = 'robot-comp-item' + (idx === selectedIndex ? ' selected' : '');
    li.textContent = tmpl ? tmpl.label : row.kind;
    li.title = row.kind;
    li.addEventListener('click', () => {
      selectedIndex = idx;
      renderList();
      renderEditor();
    });
    listEl.appendChild(li);
  });
  if (selectedIndex >= robotDocument.components.length) {
    selectedIndex = robotDocument.components.length - 1;
  }
}

function renderEditor() {
  if (!editorEl) return;
  editorEl.innerHTML = '';
  if (selectedIndex < 0 || selectedIndex >= robotDocument.components.length) {
    const p = document.createElement('p');
    p.className = 'robot-editor-empty';
    p.textContent = 'Select a component above, or add one from the list.';
    editorEl.appendChild(p);
    return;
  }

  const row = robotDocument.components[selectedIndex];
  const tmpl = getTemplate(row.kind);
  if (!tmpl) return;

  const title = document.createElement('div');
  title.className = 'robot-editor-title';
  title.textContent = tmpl.label;

  const rm = document.createElement('button');
  rm.type = 'button';
  rm.className = 'btn-danger robot-remove-btn';
  rm.textContent = 'Remove';
  rm.addEventListener('click', () => {
    robotDocument.components.splice(selectedIndex, 1);
    selectedIndex = Math.min(selectedIndex, robotDocument.components.length - 1);
    setStatus('');
    renderList();
    renderEditor();
    updateDeviceNote();
  });

  const head = document.createElement('div');
  head.className = 'robot-editor-head';
  head.appendChild(title);
  head.appendChild(rm);
  editorEl.appendChild(head);

  for (const f of tmpl.fields) {
    const wrap = document.createElement('div');
    wrap.className = 'robot-field-row';

    const lab = document.createElement('label');
    lab.textContent = f.label;
    lab.htmlFor = `rf-${selectedIndex}-${f.key}`;

    if (f.type === 'number') {
      const inp = document.createElement('input');
      inp.id = `rf-${selectedIndex}-${f.key}`;
      inp.type = 'number';
      if (f.min != null) inp.min = String(f.min);
      if (f.max != null) inp.max = String(f.max);
      if (f.step != null) inp.step = String(f.step);
      inp.value = String(row.fields[f.key] ?? f.default);
      inp.addEventListener('change', () => {
        row.fields[f.key] = String(inp.value);
        setStatus('');
      });
      wrap.appendChild(lab);
      wrap.appendChild(inp);
    } else {
      const sel = document.createElement('select');
      sel.id = `rf-${selectedIndex}-${f.key}`;
      for (const [label, val] of f.options) {
        const opt = document.createElement('option');
        opt.value = val;
        opt.textContent = label;
        sel.appendChild(opt);
      }
      sel.value = row.fields[f.key] ?? String(f.default);
      sel.addEventListener('change', () => {
        row.fields[f.key] = sel.value;
        setStatus('');
      });
      wrap.appendChild(lab);
      wrap.appendChild(sel);
    }
    editorEl.appendChild(wrap);
  }
}

function renderAll() {
  updateDeviceNote();
  renderList();
  renderEditor();
}

function addComponent(kind) {
  const tmpl = getTemplate(kind);
  if (!tmpl) return;
  robotDocument.components.push({
    kind,
    fields: /** @type {Record<string, string>} */ (
      Object.fromEntries(
        Object.entries(defaultFieldsFor(tmpl)).map(([k, v]) => [k, String(v)]),
      )
    ),
  });
  selectedIndex = robotDocument.components.length - 1;
  setStatus('');
  renderAll();
}

function newDocument() {
  robotDocument = createEmptyRobotFile(getDeviceId());
  selectedIndex = robotDocument.components.length ? 0 : -1;
  selectedFileEntry = null;
  if (fileNameInputEl) fileNameInputEl.value = '';
  setStatus('New robot file.');
  renderAll();
}

function openSelectedFile() {
  if (!selectedFileEntry?.state) return;
  try {
    robotDocument = parseRobotFileText(JSON.stringify(selectedFileEntry.state));
    selectedIndex = robotDocument.components.length ? 0 : -1;
    setStatus(`Loaded ${selectedFileEntry.name}`);
    renderAll();
  } catch (e) {
    setStatus(e instanceof Error ? e.message : 'Could not open file', true);
  }
}

async function saveCurrentFile() {
  const name = fileNameInputEl?.value?.trim();
  if (!name) {
    setStatus('Please enter a file name first.', true);
    return;
  }
  const state = JSON.parse(JSON.stringify(robotDocument));
  if (getToken()) {
    const ok = await saveCloudFile(name, state).catch(() => false);
    if (!ok) {
      setStatus('Failed to save cloud file.', true);
      return;
    }
    setStatus(`Saved ${name} [cloud]`);
  } else {
    const browser = getBrowserFiles();
    browser[name] = state;
    setBrowserFiles(browser);
    setStatus(`Saved ${name} [browser]`);
  }
  await refreshFileList();
}

/**
 * @param {{ workspace: import('blockly').WorkspaceSvg, getDeviceId: () => string }} deps
 */
export function initRobotPanel(deps) {
  workspace = deps.workspace;
  getDeviceId = deps.getDeviceId;

  panelEl = document.getElementById('robot-panel');
  listEl = document.getElementById('robot-component-list');
  fileListEl = document.getElementById('robot-file-list');
  fileNameInputEl = document.getElementById('robot-save-name');
  editorEl = document.getElementById('robot-editor');
  statusEl = document.getElementById('robot-status');
  deviceNoteEl = document.getElementById('robot-device-note');
  addSelectEl = document.getElementById('robot-add-select');
  fileInputEl = document.getElementById('robot-file-input');

  const btnNew = document.getElementById('robot-new');
  const btnOpen = document.getElementById('robot-open');
  const btnSave = document.getElementById('robot-save');
  const btnApply = document.getElementById('robot-apply');
  const btnImport = document.getElementById('robot-import-ws');
  const btnAdd = document.getElementById('robot-add-btn');
  const btnClose = document.getElementById('robot-panel-done');
  const btnToolbar = document.getElementById('btn-robot');

  robotDocument = createEmptyRobotFile(getDeviceId());
  renderAddSelect();
  renderAll();
  refreshFileList().catch(() => { /* ignore */ });
  onAuthChange(() => {
    refreshFileList().catch(() => { /* ignore */ });
  });

  btnNew?.addEventListener('click', () => newDocument());

  btnOpen?.addEventListener('click', () => openSelectedFile());

  fileInputEl?.addEventListener('change', () => {
    const f = fileInputEl.files?.[0];
    if (!f) return;
    const reader = new FileReader();
    reader.onload = () => {
      try {
        const text = String(reader.result ?? '');
        robotDocument = parseRobotFileText(text);
        selectedIndex = robotDocument.components.length ? 0 : -1;
        setStatus(`Loaded ${f.name}`);
        renderAll();
      } catch (e) {
        setStatus(e instanceof Error ? e.message : 'Could not read robot file', true);
      }
      fileInputEl.value = '';
    };
    reader.readAsText(f);
  });

  btnSave?.addEventListener('click', () => {
    saveCurrentFile().catch(() => {
      setStatus('Save failed.', true);
    });
  });

  btnApply?.addEventListener('click', () => {
    if (!workspace) return;
    try {
      applyRobotToWorkspace(workspace, robotDocument.components);
      setStatus('Applied init blocks to the workspace.');
    } catch (e) {
      setStatus(e instanceof Error ? e.message : 'Apply failed', true);
    }
  });

  btnImport?.addEventListener('click', () => {
    if (!workspace) return;
    try {
      const rows = importRobotFromWorkspace(workspace);
      if (!rows.length) {
        setStatus('No matching blocks found on the workspace.', true);
        return;
      }
      robotDocument.components = rows;
      robotDocument.device = getDeviceId();
      selectedIndex = 0;
      setStatus(`Imported ${rows.length} component(s).`);
      renderAll();
      refreshFileList().catch(() => { /* ignore */ });
    } catch (e) {
      setStatus(e instanceof Error ? e.message : 'Import failed', true);
    }
  });

  btnAdd?.addEventListener('click', () => {
    const kind = addSelectEl?.value;
    if (!kind) return;
    addComponent(kind);
    if (addSelectEl) addSelectEl.value = '';
  });

  btnClose?.addEventListener('click', () => {
    panelEl?.classList.add('hidden');
    window.dispatchEvent(new Event('resize'));
  });

  btnToolbar?.addEventListener('click', () => {
    panelEl?.classList.toggle('hidden');
    if (!panelEl?.classList.contains('hidden')) {
      robotDocument.device = getDeviceId();
      updateDeviceNote();
    }
    window.dispatchEvent(new Event('resize'));
  });
}

/** Call when the device dropdown changes so new files default correctly. */
export function syncRobotPanelDevice() {
  if (!robotDocument.components.length) {
    robotDocument.device = getDeviceId();
    updateDeviceNote();
  } else {
    updateDeviceNote();
  }
}
