const PC = {
  bg:'#F6F4EF', paper:'#FFFFFF', dark:'#16140F',
  ink:'#16140F', ink2:'#3A3630', muted:'rgba(22,20,15,0.55)', dim:'rgba(22,20,15,0.35)',
  line:'rgba(22,20,15,0.10)', line2:'rgba(22,20,15,0.20)',
  accent:'#FC4C02',
  z1:'#1E6FE3', z2:'#0E8A6E', z3:'#E0A300', z4:'#F2611A', z5:'#C8160C',
  sit:'#6366F1', stand:'#22A877', mixed:'#E0A300',
};

const ZONES = [
  { id:1, name:'S1 Oppvarm', color:PC.z1 },
  { id:2, name:'S2 Aerob',   color:PC.z2 },
  { id:3, name:'S3 Terskel', color:PC.z3 },
  { id:4, name:'S4 VO₂',     color:PC.z4 },
  { id:5, name:'S5 Sprint',  color:PC.z5 },
];

const PART_TYPES = [
  { id:'warmup',   label:'Oppvarming',  color:PC.z1 },
  { id:'song',     label:'Sangstyrt',   color:'#A855F7' },
  { id:'interval', label:'Intervaller', color:PC.z4 },
  { id:'cooldown', label:'Nedkjøling',  color:PC.z2 },
  { id:'pause',    label:'Pause',       color:'#7A7A82' },
];

const SS_OPTS = [
  { id:'sit',   label:'Sitter', color:PC.sit },
  { id:'stand', label:'Står',   color:PC.stand },
  { id:'mixed', label:'Begge',  color:PC.mixed },
];

// Standardverdier for miljøsensorer (per-økt; kan toggles av/på)
// min/max = målområdet vi vil holde oss innenfor. Utenfor = varsel på instruktør-skjerm.
const DEFAULT_SENSORS = {
  sound:    { enabled: true,  min: 50,  max: 85,   label: 'Lydnivå',    unit: 'dB',  color: PC.accent },
  temp:     { enabled: false, min: 18,  max: 24,   label: 'Temperatur', unit: '°C',  color: PC.z4 },
  humidity: { enabled: false, min: 30,  max: 60,   label: 'Fuktighet',  unit: '%',   color: PC.z2 },
  co2:      { enabled: false, min: 400, max: 1200, label: 'CO₂',       unit: 'ppm', color: PC.z1 },
};

const STORAGE_KEY = 'takt-planner-session-v1';

const zc      = id => (ZONES.find(z=>z.id===id)||ZONES[0]).color;
const ptColor = id => (PART_TYPES.find(p=>p.id===id)||PART_TYPES[0]).color;
const ptLabel = id => (PART_TYPES.find(p=>p.id===id)||PART_TYPES[0]).label;
const ssColor = id => (SS_OPTS.find(s=>s.id===id)||SS_OPTS[0]).color;
const ssLabel = id => (SS_OPTS.find(s=>s.id===id)||SS_OPTS[0]).label;

const fmt = s => {
  const a = Math.max(0, Math.round(s));
  return `${String(Math.floor(a/60)).padStart(2,'0')}:${String(a%60).padStart(2,'0')}`;
};
// "1:30" → 90 sek, "90" → 90 sek (enkelttall tolkes som sekunder).
const parseDur = v => {
  const s = String(v).trim();
  if (s.includes(':')) {
    const [m, sec] = s.split(':');
    return Math.max(0, (parseInt(m)||0)*60 + (parseInt(sec)||0));
  }
  return Math.max(0, parseInt(s)||0);
};

// Returnerer { start, end } for et intervall i økten, eller null.
const ivStartEnd = (session, ivId) => {
  let acc = 0;
  for (const p of session.parts) for (const iv of p.intervals) {
    if (iv.id===ivId) return { start:acc, end:acc+iv.duration };
    acc += iv.duration;
  }
  return null;
};

// Alle grense-tidspunkter (interval-starter + slutten av økten) — for snap.
const intervalBoundaries = session => {
  const out = [0]; let acc = 0;
  for (const p of session.parts) for (const iv of p.intervals) {
    acc += iv.duration; out.push(acc);
  }
  return out;
};

// Snap til nærmeste grenseverdi innenfor terskel (sek). Returnerer { sec, snapped }.
const snapSec = (sec, boundaries, thresholdSec) => {
  let best = sec, bestDiff = thresholdSec, snapped = false;
  for (const b of boundaries) {
    const d = Math.abs(b - sec);
    if (d <= bestDiff) { best = b; bestDiff = d; snapped = true; }
  }
  return { sec: best, snapped };
};

// Flytt grensen mellom forrige intervall og dette → newStartSec.
// Forrige intervall vokser/krymper; dette krymper/vokser. Total øktlengde bevares.
// Returnerer { warn } hvis verdien måtte klampes.
const MIN_IV = 10;
const adjustIvStart = (session, ivId, newStartSec) => {
  const list = []; for (const p of session.parts) for (const iv of p.intervals) list.push(iv);
  const idx = list.findIndex(x => x.id===ivId);
  if (idx <= 0) return { error:'Første intervall kan ikke flyttes' };
  const prev = list[idx-1], cur = list[idx];
  let acc = 0; for (let i=0;i<idx;i++) acc += list[i].duration;
  const curStart = acc;
  let target = Math.round(newStartSec);
  const minStart = curStart - prev.duration + MIN_IV;
  const maxStart = curStart + cur.duration - MIN_IV;
  let warn = null;
  if (target < minStart) { target = minStart; warn = `Forrige intervall må være minst ${MIN_IV}s — justerte`; }
  if (target > maxStart) { target = maxStart; warn = `Intervallet må være minst ${MIN_IV}s — justerte`; }
  const delta = target - curStart;
  prev.duration += delta;
  cur.duration  -= delta;
  return { warn };
};

// Flytt grensen mellom dette intervall og neste → newEndSec.
// Hvis det er siste intervall i økten: utvider/krymper bare dette.
const adjustIvEnd = (session, ivId, newEndSec) => {
  const list = []; for (const p of session.parts) for (const iv of p.intervals) list.push(iv);
  const idx = list.findIndex(x => x.id===ivId);
  if (idx < 0) return { error:'Fant ikke intervall' };
  const cur = list[idx];
  const next = list[idx+1] || null;
  let acc = 0; for (let i=0;i<idx;i++) acc += list[i].duration;
  const curStart = acc;
  const curEnd = curStart + cur.duration;
  let target = Math.round(newEndSec);
  const minEnd = curStart + MIN_IV;
  const maxEnd = next ? curEnd + next.duration - MIN_IV : Number.POSITIVE_INFINITY;
  let warn = null;
  if (target < minEnd) { target = minEnd; warn = `Intervallet må være minst ${MIN_IV}s — justerte`; }
  if (next && target > maxEnd) { target = maxEnd; warn = `Neste intervall må være minst ${MIN_IV}s — justerte`; }
  const delta = target - curEnd;
  cur.duration += delta;
  if (next) next.duration -= delta;
  return { warn };
};

// uid()/reseedUid()/defaultSession()/loadInitialSession() delegerer til
// shared/program.js (window.TaktProgram) — se den filen for
// programvalidering og uid-telleren. Delegeringen er lazy (kalt inni
// funksjonskropper), så det spiller ingen rolle om denne filen laster før
// eller etter shared/program.js.
const uid      = (...a) => window.TaktProgram.uid(...a);
const reseedUid = (...a) => window.TaktProgram.reseedUid(...a);
const deepCopy = x => JSON.parse(JSON.stringify(x));
const partDur  = p => p.intervals.reduce((a,iv)=>a+iv.duration,0);
const totalDur = s => s.parts.reduce((a,p)=>a+partDur(p),0);

function defaultSession() {
  return window.TaktProgram.defaultProgram();
}

// Migrate-eller-default: leser localStorage, validerer schema, fyller på nye felter for gamle filer.
function loadInitialSession() {
  try {
    const raw = typeof localStorage !== 'undefined' ? localStorage.getItem(STORAGE_KEY) : null;
    if (!raw) return defaultSession();
    const s = JSON.parse(raw);
    if (!s || !Array.isArray(s.parts) || !Array.isArray(s.tracks)) return defaultSession();
    return window.TaktProgram.normalizeProgram(s);
  } catch {
    return defaultSession();
  }
}

function usePlannerState() {
  const [hist, setHist] = React.useState(() => ({
    past:[], present:loadInitialSession(), future:[]
  }));
  // cloudId/dirty er persistens-metadata, ikke øktinnhold — holdes utenfor
  // undo/redo-historikken med vilje.
  const [cloudId, setCloudId] = React.useState(null);
  const [dirty,   setDirty]   = React.useState(false);

  // Autosave til localStorage (debounced 400ms). Fanger både dispatch og silentUpdate
  // slik at vi ikke mister arbeid på refresh midt i en dra-bevegelse.
  React.useEffect(() => {
    const t = setTimeout(() => {
      try { localStorage.setItem(STORAGE_KEY, JSON.stringify(hist.present)); } catch {}
    }, 400);
    return () => clearTimeout(t);
  }, [hist.present]);

  const dispatch = React.useCallback(fn => {
    setHist(h => {
      const next = deepCopy(h.present);
      fn(next);
      return { past:[...h.past.slice(-49), h.present], present:next, future:[] };
    });
    setDirty(true);
  }, []);

  const silentUpdate = React.useCallback(fn => {
    setHist(h => {
      const next = deepCopy(h.present);
      fn(next);
      return { ...h, present:next };
    });
    setDirty(true);
  }, []);

  const undo = React.useCallback(() => {
    setHist(h => {
      if (!h.past.length) return h;
      return { past:h.past.slice(0,-1), present:h.past.at(-1), future:[h.present,...h.future] };
    });
    setDirty(true);
  }, []);

  const redo = React.useCallback(() => {
    setHist(h => {
      if (!h.future.length) return h;
      return { past:[...h.past, h.present], present:h.future[0], future:h.future.slice(1) };
    });
    setDirty(true);
  }, []);

  // opts.cloudId/opts.dirty lar kallere som laster et sky- eller delt
  // program sette riktig lenke-status i samme kall — bakoverkompatibelt,
  // eksisterende kallsteder (newSession/importFn) trenger ingen endring.
  const setSession = React.useCallback((s, opts) => {
    reseedUid(s);
    setHist(h => ({ past:[...h.past.slice(-49),h.present], present:s, future:[] }));
    setCloudId(opts && opts.cloudId !== undefined ? opts.cloudId : null);
    setDirty(opts && opts.dirty !== undefined ? opts.dirty : false);
  }, []);

  // Kalles etter en vellykket sky-lagring — oppdaterer KUN persistens-status,
  // rører ikke selve øktinnholdet eller undo/redo-historikken.
  const markSaved = React.useCallback((newCloudId) => {
    setCloudId(newCloudId);
    setDirty(false);
  }, []);

  return {
    session:hist.present,
    canUndo:hist.past.length>0,
    canRedo:hist.future.length>0,
    dispatch, silentUpdate, undo, redo, setSession,
    cloudId, dirty, markSaved,
  };
}

Object.assign(window, {
  PC, ZONES, PART_TYPES, SS_OPTS, DEFAULT_SENSORS, STORAGE_KEY, MIN_IV,
  zc, ptColor, ptLabel, ssColor, ssLabel,
  fmt, parseDur, uid, deepCopy, partDur, totalDur,
  ivStartEnd, intervalBoundaries, snapSec, adjustIvStart, adjustIvEnd,
  defaultSession, loadInitialSession, usePlannerState,
});
