// 스케줄 계산 로직: 현재 시각에 어떤 미디어(플레이리스트)를 재생할지, 밝기는 얼마인지 결정
// 서버가 진실의 원천(source of truth)이며, 계산 결과를 플레이어/미리보기에 브로드캐스트한다.

function hmToMinutes(hm) {
  const [h, m] = String(hm).split(':').map(Number);
  return (h || 0) * 60 + (m || 0);
}

function nowParts(date = new Date()) {
  return {
    minutes: date.getHours() * 60 + date.getMinutes(),
    weekday: date.getDay(), // 0=일요일 ... 6=토요일
  };
}

// 시간 구간이 자정을 넘어가는 경우도 지원 (예: 22:00 ~ 02:00)
function inRange(cur, start, end) {
  if (start === end) return true; // 하루 종일
  if (start < end) return cur >= start && cur < end;
  // 자정 넘김
  return cur >= start || cur < end;
}

// 활성 스케줄 찾기: 요일 + 시간 조건을 만족하는 enabled 스케줄.
// 여러 개가 겹치면 시작 시각이 늦은(더 구체적인) 것을 우선.
export function resolveActiveSchedule(db, date = new Date()) {
  const { minutes, weekday } = nowParts(date);
  const candidates = db.schedules
    .filter((s) => s.enabled !== false)
    .filter((s) => Array.isArray(s.days) && s.days.includes(weekday))
    .filter((s) => inRange(minutes, hmToMinutes(s.start), hmToMinutes(s.end)));

  if (candidates.length === 0) return null;

  candidates.sort((a, b) => hmToMinutes(b.start) - hmToMinutes(a.start));
  return candidates[0];
}

function pad2(n) { return String(n).padStart(2, '0'); }
function ymd(date) { return `${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())}`; }
function dateInRange(today, start, end) {
  if (start && today < start) return false; // 'YYYY-MM-DD' 문자열 비교 = 날짜 비교
  if (end && today > end) return false;
  return true;
}
function inAnySlot(minutes, slots) {
  return (slots || []).some((s) => inRange(minutes, hmToMinutes(s.start), hmToMinutes(s.end)));
}

// 현재 시각에 활성화된 포스터(현수막) 목록 — 날짜범위 + 요일 + 시간대 조건
export function resolveActivePosters(db, date = new Date()) {
  const { minutes, weekday } = nowParts(date);
  const today = ymd(date);
  return (db.posters || [])
    .filter((p) => p.enabled !== false)
    .filter((p) => dateInRange(today, p.startDate, p.endDate))
    .filter((p) => !p.days || p.days.length === 0 || p.days.includes(weekday))
    .filter((p) => inAnySlot(minutes, p.slots));
}

// 화면 전원(취침) 스케줄 — 지정 요일/시간이면 화면 끔(검은 화면)
export function isPowerOff(power, date = new Date()) {
  if (!power || !power.enabled) return false;
  const { minutes, weekday } = nowParts(date);
  if (power.days && power.days.length && !power.days.includes(weekday)) return false;
  return inAnySlot(minutes, [{ start: power.offStart, end: power.offEnd }]);
}

// 밝기 계산 (0~100)
export function resolveBrightness(db, date = new Date()) {
  const b = db.settings.brightness;
  if (!b) return 100;
  if (b.mode === 'manual') return clamp(b.manual, 0, 100);

  // auto: 주간/야간 시작 시각을 기준으로 구간 판정
  const { minutes } = nowParts(date);
  const dayStart = hmToMinutes(b.day.start);
  const nightStart = hmToMinutes(b.night.start);

  // 주간 구간 = [dayStart, nightStart)  (자정 넘김 지원)
  const isDay = inRange(minutes, dayStart, nightStart);
  return clamp(isDay ? b.day.value : b.night.value, 0, 100);
}

function clamp(v, min, max) {
  v = Number(v);
  if (Number.isNaN(v)) return min;
  return Math.max(min, Math.min(max, v));
}

// 플레이어에게 보낼 최종 재생 지시(directive) 생성
export function buildDirective(db, date = new Date()) {
  const brightness = resolveBrightness(db, date);
  const resolution = db.settings.resolution;
  const scaleMode = db.settings.scaleMode || 'contain';
  const idleMode = db.settings.idleMode || 'black';
  const overlay = db.settings.overlay || {};
  const layoutCfg = db.settings.layout || { preset: 'single', subMediaIds: [] };
  const subPlaylist = (layoutCfg.subMediaIds || []).map((id) => db.media.find((m) => m.id === id)).filter(Boolean);
  const layout = { preset: layoutCfg.preset || 'single', subPlaylist };
  const common = { brightness, resolution, scaleMode, idleMode, overlay, layout, serverTime: date.getTime() };

  // 1순위: 긴급 송출
  if (db.emergency?.active && db.emergency.mediaId) {
    const media = db.media.find((m) => m.id === db.emergency.mediaId);
    if (media) {
      return { mode: 'emergency', playlist: [media], scheduleId: null, scheduleName: '긴급 송출', ...common };
    }
  }

  // 2순위: 화면 전원(취침) 스케줄 → 검은 화면(오버레이도 끔)
  if (isPowerOff(db.settings.power, date)) {
    return { mode: 'off', playlist: [], scheduleId: null, scheduleName: '화면 꺼짐(취침)', ...common, overlay: {} };
  }

  // 3순위: 포스터/현수막 (영상·유튜브 스케줄보다 우선)
  const posters = resolveActivePosters(db, date);
  if (posters.length) {
    const playlist = posters
      .map((p) => {
        const m = db.media.find((x) => x.id === p.mediaId);
        return m ? { ...m, durationSec: p.displaySeconds || 15 } : null;
      })
      .filter(Boolean);
    if (playlist.length) {
      return {
        mode: 'poster',
        playlist,
        scheduleId: null,
        scheduleName: posters.length > 1 ? `포스터 ${posters.length}개 순환` : (posters[0].name || '포스터/현수막'),
        ...common,
      };
    }
  }

  // 3순위: 일반 영상 스케줄
  const active = resolveActiveSchedule(db, date);
  if (!active) {
    return { mode: 'idle', playlist: [], scheduleId: null, scheduleName: null, ...common };
  }

  // 스케줄이 재사용 재생목록(캠페인)을 참조하면 거기서 미디어를 가져옴
  const mediaIds = active.playlistId
    ? (((db.playlists || []).find((p) => p.id === active.playlistId) || {}).mediaIds || [])
    : (active.mediaIds || []);
  const playlist = mediaIds.map((id) => db.media.find((m) => m.id === id)).filter(Boolean);

  return {
    mode: playlist.length ? 'schedule' : 'idle',
    playlist,
    scheduleId: active.id,
    scheduleName: active.name || '스케줄',
    ...common,
  };
}

// 두 directive가 재생 관점에서 동일한지 (heartbeat 시 불필요한 리로드 방지)
export function directiveKey(d) {
  return JSON.stringify({
    mode: d.mode,
    ids: d.playlist.map((m) => m.id),
    scheduleId: d.scheduleId,
    brightness: d.brightness,
    resolution: d.resolution,
    scaleMode: d.scaleMode,
    idleMode: d.idleMode,
    overlay: d.overlay,
    layout: d.layout && { preset: d.layout.preset, ids: (d.layout.subPlaylist || []).map((m) => m.id) },
  });
}
