import { atom } from 'nanostores';
import { useStore } from '@nanostores/react';
import { useMemo, useState } from 'react';
import cx from '@src/cx.mjs';
import { useT } from '@src/i18n.mjs';
import { useSettings } from '@src/settings.mjs';
import { ActionButton } from '../button/action-button.jsx';
import { Textbox } from './SettingsTab.jsx';
import { DEFAULT_PROJECTS, DEFAULT_VERSION } from './default-projects.mjs';

// Almacen propio de proyectos locales, independiente del autosave de patterns.
// Se persiste en localStorage bajo la clave 'strudel-projects'.
const KEY = 'strudel-projects';
const VKEY = 'strudel-projects-version';
const LHKEY = 'strudel-projects-listh'; // altura (px) de la lista de samples (redimensionable)
const FKEY = 'strudel-projects-folders-open'; // carpetas abiertas (persistente)

// carpeta para lo guardado sin carpeta + orden fijo de las de fabrica
const SIN_CARPETA = 'mis proyectos';
const FOLDER_ORDER = [
  SIN_CARPETA,
  'remixes 80s',
  'remixes 90s',
  'remixes 2000s',
  'acid largo',
  'techno estilos',
  'acid conceptual',
  'detalle',
  'bases',
];

const load = () => {
  try {
    return JSON.parse(localStorage.getItem(KEY)) ?? [];
  } catch {
    return [];
  }
};

// Siembra SOLO-AÑADIR: cuando sube DEFAULT_VERSION, agrega los tracks de fabrica
// que falten por nombre. NUNCA borra nada (ni los de fabrica ni los tuyos) —
// la idea es ir acumulando una libreria para pinchar.
// Ademas rellena la carpeta (folder) de los tracks de fabrica ya guardados que
// no la tengan — solo metadatos, nunca toca el codigo.
const migrate = (list) => {
  try {
    const v = Number(localStorage.getItem(VKEY) || 0);
    if (v >= DEFAULT_VERSION) return list;
    const folderByName = Object.fromEntries(DEFAULT_PROJECTS.map((p) => [p.name, p.folder]));
    const withFolders = list.map((p) =>
      p.folder == null && folderByName[p.name] != null ? { ...p, folder: folderByName[p.name] } : p,
    );
    const names = new Set(withFolders.map((p) => p.name));
    const toAdd = DEFAULT_PROJECTS.filter((p) => !names.has(p.name)).map((p) => ({ ...p, factory: true }));
    const merged = [...withFolders, ...toAdd.slice().reverse()];
    localStorage.setItem(KEY, JSON.stringify(merged));
    localStorage.setItem(VKEY, String(DEFAULT_VERSION));
    return merged;
  } catch {
    return list;
  }
};

export const $projects = atom(migrate(load()));
// nombre del sample cargado en el editor (compartido con la barra de transporte)
export const $playing = atom(null);
// true = se acaba de cargar un track: la proxima evaluacion arranca el loop
// desde el ciclo 0 (editar y actualizar en vivo no pasa por aqui)
export const $resetPending = atom(false);

const persist = (list) => {
  try {
    localStorage.setItem(KEY, JSON.stringify(list));
  } catch {
    /* SSR / storage no disponible */
  }
  $projects.set(list);
};

// ── helpers de análisis del código (compartidos con TransportBar) ───────────
// evalua una expresion tipo "132/4", "132/60/4" (division encadenada) o "33"
const evalCpm = (expr) => {
  const nums = String(expr).match(/[\d.]+/g);
  if (!nums) return 30;
  const hasDiv = /\//.test(String(expr));
  if (!hasDiv || nums.length === 1) return parseFloat(nums[0]);
  return nums.map(parseFloat).reduce((a, b) => a / b);
};

// saca BPM, cps y longitud del loop (en ciclos) de un track.
// - BPM = cpm * 4 (convencion setcpm(BPM/4))
// - loop: si hay arrange(...) => suma de ciclos de cada seccion; si no, la
//   "variable mas larga" (mayor .slow(N) o el <...> con mas pasos). Default 1.
export const parseMeta = (code = '') => {
  const scpm = code.match(/setcpm\s*\(([^)]*)\)/);
  const scps = code.match(/setcps\s*\(([^)]*)\)/);
  let cpm = 30;
  if (scpm) cpm = evalCpm(scpm[1]);
  else if (scps) cpm = evalCpm(scps[1]) * 60;
  const bpm = Math.round(cpm * 4);
  const cps = cpm / 60 || 0.5;

  // para localizar el arrange() sin tropezar con la mini-notacion, quitamos strings
  const stripped = code
    .replace(/"[^"]*"/g, '""')
    .replace(/'[^']*'/g, "''")
    .replace(/`[^`]*`/g, '``');
  let loop = null;
  const ai = stripped.indexOf('arrange');
  if (ai >= 0) {
    const open = stripped.indexOf('(', ai);
    if (open >= 0) {
      let depth = 0;
      let end = -1;
      for (let j = open; j < stripped.length; j++) {
        const c = stripped[j];
        if (c === '(') depth++;
        else if (c === ')') {
          depth--;
          if (depth === 0) {
            end = j;
            break;
          }
        }
      }
      if (end > open) {
        const body = stripped.slice(open + 1, end);
        let d = 0;
        let sum = 0;
        let found = false;
        for (let k = 0; k < body.length; k++) {
          const c = body[k];
          if (c === '[') {
            if (d === 0) {
              const m = body.slice(k + 1).match(/^\s*([\d.]+)/);
              if (m) {
                sum += parseFloat(m[1]);
                found = true;
              }
            }
            d++;
          } else if (c === ']') d = Math.max(0, d - 1);
        }
        if (found) loop = sum;
      }
    }
  }
  if (loop == null) {
    let max = 1;
    for (const m of code.matchAll(/\.slow\(\s*([\d.]+)\s*\)/g)) max = Math.max(max, parseFloat(m[1]));
    for (const m of code.matchAll(/<([^<>]*)>/g)) {
      const n = m[1].trim().split(/\s+/).filter(Boolean).length;
      if (n) max = Math.max(max, n);
    }
    loop = max;
  }
  return { bpm, cps, loopCycles: loop || 1 };
};

// color del boton de BPM por tramos (para saber que mezcla bien con que)
export const bpmColor = (bpm) => {
  if (bpm >= 145) return { bg: '#111827', fg: '#f9fafb' }; // negro (145+)
  if (bpm >= 135) return { bg: '#f97316', fg: '#1a1a1a' }; // naranja (135-145)
  if (bpm >= 125) return { bg: '#eab308', fg: '#1a1a1a' }; // amarillo (125-135)
  if (bpm >= 115) return { bg: '#22c55e', fg: '#0a0a0a' }; // verde (115-125)
  return { bg: '#3b82f6', fg: '#f9fafb' }; // azul (100-115 y menos)
};

export const fmt = (s) => {
  if (!isFinite(s) || s < 0) s = 0;
  const m = Math.floor(s / 60);
  const sec = Math.floor(s % 60);
  return `${m}:${String(sec).padStart(2, '0')}`;
};

export function ProjectsTab({ context }) {
  const { fontFamily } = useSettings();
  const t = useT();
  const projects = useStore($projects);
  const playingName = useStore($playing);
  const [name, setName] = useState('');
  const [copied, setCopied] = useState(null);
  const [previewed, setPreviewed] = useState(null); // sample mostrado en la pantalla de lectura (NO se ejecuta)
  const [listH, setListH] = useState(() => {
    try {
      return Number(localStorage.getItem(LHKEY)) || 200;
    } catch {
      return 200;
    }
  });
  // carpetas abiertas (Set de nombres); persiste entre sesiones
  const [openFolders, setOpenFolders] = useState(() => {
    try {
      return new Set(JSON.parse(localStorage.getItem(FKEY)) ?? []);
    } catch {
      return new Set();
    }
  });

  const toggleFolder = (f) => {
    setOpenFolders((prev) => {
      const next = new Set(prev);
      if (next.has(f)) next.delete(f);
      else next.add(f);
      try {
        localStorage.setItem(FKEY, JSON.stringify([...next]));
      } catch {
        /* noop */
      }
      return next;
    });
  };

  const editor = () => context.editorRef.current;

  // BPM/loop de cada sample (memo, solo recalcula si cambia la lista)
  const metas = useMemo(() => {
    const out = {};
    for (const p of projects) out[p.name] = parseMeta(p.code);
    return out;
  }, [projects]);

  // agrupar por carpeta manteniendo el orden de la lista (mas reciente primero)
  const folders = useMemo(() => {
    const map = new Map();
    for (const p of projects.slice().reverse()) {
      const f = p.folder || SIN_CARPETA;
      if (!map.has(f)) map.set(f, []);
      map.get(f).push(p);
    }
    const pos = (f) => {
      const i = FOLDER_ORDER.indexOf(f);
      return i === -1 ? 0.5 : i; // carpetas propias desconocidas: tras "mis proyectos"
    };
    return [...map.entries()].sort((a, b) => pos(a[0]) - pos(b[0]) || a[0].localeCompare(b[0]));
  }, [projects]);

  const saveCurrent = () => {
    const code = editor()?.code ?? '';
    if (!code.trim()) return;
    // "carpeta/nombre" guarda dentro de esa carpeta; sin barra va a "mis proyectos"
    const raw = name.trim() || `project ${projects.length + 1}`;
    const slash = raw.indexOf('/');
    const folder = slash > 0 ? raw.slice(0, slash).trim() : undefined;
    const title = (slash > 0 ? raw.slice(slash + 1).trim() : raw) || raw;
    const rest = projects.filter((p) => p.name !== title);
    persist([...rest, { name: title, code, created_at: Date.now(), ...(folder ? { folder } : {}) }]);
    setName('');
  };

  const copyProject = (p) => {
    navigator.clipboard?.writeText?.(p.code);
    setCopied(p.name);
    setTimeout(() => setCopied((c) => (c === p.name ? null : c)), 1200);
  };

  // click en el nombre = SOLO leer en la pantalla de abajo (no ejecuta, no toca lo que suena)
  const preview = (p) => setPreviewed(p);

  // cargar = reemplaza el editor con este track (no lo evalua; play/skip de la barra lo hacen)
  const loadProject = (p) => {
    const ed = editor();
    if (!ed) return;
    ed.setCode(p.code);
    setPreviewed(p);
    $playing.set(p.name);
    $resetPending.set(true);
  };

  const del = (p) => {
    persist(projects.filter((x) => x.name !== p.name));
    if (previewed?.name === p.name) setPreviewed(null);
  };

  const onKeyDown = (e) => {
    if (e.key === 'Enter') saveCurrent();
  };

  // ── redimensionar la lista arrastrando el divisor ──
  const startDrag = (e) => {
    e.preventDefault();
    const startY = e.clientY;
    const startH = listH;
    let lastH = startH;
    const move = (ev) => {
      lastH = Math.max(64, Math.min(720, startH + (ev.clientY - startY)));
      setListH(lastH);
    };
    const up = () => {
      document.removeEventListener('mousemove', move);
      document.removeEventListener('mouseup', up);
      try {
        localStorage.setItem(LHKEY, String(Math.round(lastH)));
      } catch {
        /* noop */
      }
    };
    document.addEventListener('mousemove', move);
    document.addEventListener('mouseup', up);
  };

  return (
    <div className="w-full h-full text-foreground flex flex-col overflow-hidden" style={{ fontFamily }}>
      {/* guardar */}
      <div className="flex gap-2 items-center px-2 py-2 border-b border-muted shrink-0">
        <Textbox
          className="w-full border-0"
          placeholder={t('projectPlaceholder')}
          value={name}
          onChange={setName}
          onKeyDown={onKeyDown}
        />
        <ActionButton label={t('save')} onClick={saveCurrent} className="px-2 border border-muted rounded-sm" />
      </div>

      {/* LISTA de samples agrupada en carpetas plegables (altura redimensionable) */}
      <div className="overflow-auto px-2 py-1 text-sm shrink-0" style={{ height: listH }}>
        {projects.length === 0 && <p className="opacity-60 py-2">{t('noProjects')}</p>}
        {folders.map(([folder, items]) => {
          const open = openFolders.has(folder);
          const sonando = playingName != null && items.some((p) => p.name === playingName);
          return (
            <div key={folder}>
              {/* cabecera de carpeta: click = plegar/desplegar */}
              <div
                className={cx(
                  'flex items-center gap-2 py-1 cursor-pointer select-none sticky top-0 bg-background',
                  'border-b border-muted font-bold uppercase tracking-wide text-xs',
                  open ? 'text-cyan-300' : 'opacity-80 hover:opacity-100',
                )}
                title={open ? `plegar ${folder}` : `desplegar ${folder}`}
                onClick={() => toggleFolder(folder)}
              >
                <span className="w-3 text-center">{open ? '▾' : '▸'}</span>
                <span className="truncate">
                  📁 {folder === SIN_CARPETA ? t('myProjects') : folder}
                  {sonando && <span className="text-cyan-400 ml-1">●</span>}
                </span>
                <span className="ml-auto opacity-50 tabular-nums normal-case">{items.length}</span>
              </div>
              {open &&
                items.map((p) => {
                  const m = metas[p.name] || { bpm: 0 };
                  const col = bpmColor(m.bpm);
                  return (
                    <div
                      key={p.name}
                      className={cx(
                        'flex items-center gap-2 py-1 pl-4 border-b border-muted/40',
                        previewed?.name === p.name && 'bg-cyan-400/10',
                        playingName === p.name && 'ring-1 ring-cyan-400/40 rounded-sm',
                      )}
                    >
                      {/* copiar: a la IZQUIERDA y enmarcado, lejos de la ✕ para no borrar sin querer */}
                      <ActionButton
                        label={copied === p.name ? `✓ ${t('copied')}` : `⧉ ${t('copy')}`}
                        onClick={() => copyProject(p)}
                        className={cx(
                          'px-2 py-0.5 border rounded shrink-0 font-bold',
                          copied === p.name
                            ? 'border-green-400 text-green-400'
                            : 'border-cyan-400/70 text-cyan-300 hover:bg-cyan-400/10',
                        )}
                      />
                      <span
                        className="truncate flex-1 cursor-pointer"
                        title={`${t('read')} ${p.name}`}
                        onClick={() => preview(p)}
                      >
                        {p.name}
                      </span>
                      {/* BPM: a la DERECHA, boton coloreado por tramo (mezcla) */}
                      <span
                        className="text-[10px] font-bold px-1.5 py-0.5 rounded shrink-0 tabular-nums"
                        title={`${m.bpm} BPM`}
                        style={{ backgroundColor: col.bg, color: col.fg }}
                      >
                        {m.bpm}
                      </span>
                      <ActionButton
                        label={t('load')}
                        onClick={() => loadProject(p)}
                        className="px-2 opacity-70 hover:opacity-100 shrink-0"
                      />
                      <ActionButton
                        label="✕"
                        onClick={() => del(p)}
                        className="text-red-400 px-1 opacity-40 hover:opacity-100 shrink-0"
                      />
                    </div>
                  );
                })}
            </div>
          );
        })}
      </div>

      {/* DIVISOR arrastrable (redimensiona lista vs. pantalla de lectura) */}
      <div
        onMouseDown={startDrag}
        title="arrastra para redimensionar"
        className="h-1.5 shrink-0 cursor-row-resize bg-muted hover:bg-cyan-400/60"
      />

      {/* PANTALLA DE LECTURA (preview): muestra el sample clicado SIN ejecutar nada */}
      <div className="grow overflow-auto px-2 py-2 min-h-0">
        {previewed ? (
          <>
            <div className="flex items-center gap-2 mb-1">
              <span className="text-xs opacity-70 truncate">
                {t('previewTitle')} · {previewed.name}
              </span>
              <ActionButton
                label={t('loadInEditor')}
                onClick={() => loadProject(previewed)}
                className="ml-auto text-cyan-300 border border-cyan-400/60 rounded px-1.5 text-xs shrink-0"
              />
            </div>
            <pre className="font-mono text-xs whitespace-pre-wrap leading-snug opacity-90 select-text">
              {previewed.code}
            </pre>
          </>
        ) : (
          <p className="opacity-50 text-xs">{t('previewHelp')}</p>
        )}
      </div>
    </div>
  );
}
