import {
  endOfWeekSunday,
  parseIsoDate,
  quarterEndDate,
  quarterStartDate,
  startOfDay,
} from './boardCalendar';
import { fmtPeriodRange, parseMilestonePeriods } from './milestonePeriods';
import type { Milestone, Task } from '../types';

export interface TaskWeekFocus {
  milestoneId: string;
  milestoneTitle: string;
  periodLabel: string;
  progress: number;
}

export interface TaskFocusOptions {
  weekMonday: Date;
  weekLensActive?: boolean;
  quarter?: string;
}

type TaskWithMilestones = Task & { milestones?: Milestone[] };

interface PeriodSpan {
  milestoneId: string;
  milestoneTitle: string;
  periodLabel: string;
  progress: number;
  startMs: number;
  endMs: number;
  sortDue: string;
  sortStart: string;
  order: number;
}

function periodOverlapsRange(
  startIso: string | null | undefined,
  endIso: string | null | undefined,
  rangeStart: Date,
  rangeEnd: Date,
): boolean {
  const startMs = startIso ? parseIsoDate(startIso)?.getTime() : null;
  const endMs = endIso ? parseIsoDate(endIso)?.getTime() : null;
  if (startMs == null && endMs == null) return false;

  const spanStart = startMs ?? endMs!;
  const spanEnd = endMs ?? startMs!;
  const rs = startOfDay(rangeStart).getTime();
  const re = startOfDay(rangeEnd).getTime();
  return spanStart <= re && spanEnd >= rs;
}

function collectPeriodSpans(task: TaskWithMilestones): PeriodSpan[] {
  const spans: PeriodSpan[] = [];

  for (const ms of task.milestones ?? []) {
    const title = ms.title?.trim();
    if (!title) continue;

    const periods = parseMilestonePeriods(ms);
    if (periods.length > 0) {
      for (const period of periods) {
        if (!period.startDate && !period.dueDate) continue;
        const startMs = period.startDate
          ? (parseIsoDate(period.startDate)?.getTime() ?? Number.NaN)
          : Number.NaN;
        const endMs = period.dueDate
          ? (parseIsoDate(period.dueDate)?.getTime() ?? Number.NaN)
          : Number.NaN;
        const resolvedStart = Number.isFinite(startMs) ? startMs : endMs;
        const resolvedEnd = Number.isFinite(endMs) ? endMs : startMs;
        if (!Number.isFinite(resolvedStart) || !Number.isFinite(resolvedEnd)) continue;
        spans.push({
          milestoneId: ms.id,
          milestoneTitle: title,
          periodLabel: fmtPeriodRange(period) || '—',
          progress: ms.progress ?? 0,
          startMs: Math.min(resolvedStart, resolvedEnd),
          endMs: Math.max(resolvedStart, resolvedEnd),
          sortDue: period.dueDate || period.startDate || '9999-12-31',
          sortStart: period.startDate || period.dueDate || '9999-12-31',
          order: ms.order ?? 0,
        });
      }
      continue;
    }

    if (!ms.startDate && !ms.dueDate) continue;
    const startMs = ms.startDate ? (parseIsoDate(ms.startDate)?.getTime() ?? Number.NaN) : Number.NaN;
    const endMs = ms.dueDate ? (parseIsoDate(ms.dueDate)?.getTime() ?? Number.NaN) : Number.NaN;
    const resolvedStart = Number.isFinite(startMs) ? startMs : endMs;
    const resolvedEnd = Number.isFinite(endMs) ? endMs : startMs;
    if (!Number.isFinite(resolvedStart) || !Number.isFinite(resolvedEnd)) continue;
    spans.push({
      milestoneId: ms.id,
      milestoneTitle: title,
      periodLabel:
        fmtPeriodRange({ startDate: ms.startDate ?? '', dueDate: ms.dueDate ?? '' }) || '—',
      progress: ms.progress ?? 0,
      startMs: Math.min(resolvedStart, resolvedEnd),
      endMs: Math.max(resolvedStart, resolvedEnd),
      sortDue: ms.dueDate || ms.startDate || '9999-12-31',
      sortStart: ms.startDate || ms.dueDate || '9999-12-31',
      order: ms.order ?? 0,
    });
  }

  return spans;
}

function pickBestSpan(spans: PeriodSpan[]): PeriodSpan | null {
  if (spans.length === 0) return null;
  const sorted = [...spans].sort((a, b) => {
    const dueCmp = a.sortDue.localeCompare(b.sortDue);
    if (dueCmp !== 0) return dueCmp;
    const startCmp = a.sortStart.localeCompare(b.sortStart);
    if (startCmp !== 0) return startCmp;
    return a.order - b.order;
  });
  return sorted[0];
}

function spansOverlappingRange(
  task: TaskWithMilestones,
  rangeStart: Date,
  rangeEnd: Date,
): PeriodSpan[] {
  return collectPeriodSpans(task).filter((span) => {
    const rs = startOfDay(rangeStart).getTime();
    const re = startOfDay(rangeEnd).getTime();
    return span.startMs <= re && span.endMs >= rs;
  });
}

function spanToFocus(span: PeriodSpan): TaskWeekFocus {
  return {
    milestoneId: span.milestoneId,
    milestoneTitle: span.milestoneTitle,
    periodLabel: span.periodLabel,
    progress: span.progress,
  };
}

/** 선택 주(월요일 기준)에 해당하는 업무 1건 — 겹치면 마감이 가장 가까운 것 */
export function resolveTaskWeekFocus(
  task: TaskWithMilestones,
  weekMonday: Date,
): TaskWeekFocus | null {
  const weekStart = startOfDay(weekMonday);
  const weekEnd = endOfWeekSunday(weekMonday);
  const best = pickBestSpan(spansOverlappingRange(task, weekStart, weekEnd));
  return best ? spanToFocus(best) : null;
}

export function resolveTaskWeekMilestoneId(
  task: TaskWithMilestones,
  weekMonday: Date,
): string | null {
  return resolveTaskWeekFocus(task, weekMonday)?.milestoneId ?? null;
}

function resolveTaskQuarterFocus(task: TaskWithMilestones, quarter: string): TaskWeekFocus | null {
  const rangeStart = startOfDay(quarterStartDate(quarter));
  const rangeEnd = startOfDay(quarterEndDate(quarter));
  const best = pickBestSpan(spansOverlappingRange(task, rangeStart, rangeEnd));
  return best ? spanToFocus(best) : null;
}

/** 기준일과 가장 가까운 업무 — 진행 중 우선, 없으면 가장 최근에 끝난 업무 */
export function resolveTaskNearestMilestoneId(
  task: TaskWithMilestones,
  refDate: Date,
): string | null {
  const refMs = startOfDay(refDate).getTime();
  const spans = collectPeriodSpans(task);
  if (spans.length === 0) return null;

  let best: { span: PeriodSpan; score: number } | null = null;
  for (const span of spans) {
    let score: number;
    if (refMs >= span.startMs && refMs <= span.endMs) {
      score = 0;
    } else if (refMs > span.endMs) {
      score = refMs - span.endMs;
    } else {
      score = span.startMs - refMs + Number.MAX_SAFE_INTEGER / 2;
    }

    if (
      !best ||
      score < best.score ||
      (score === best.score && span.endMs > best.span.endMs) ||
      (score === best.score && span.endMs === best.span.endMs && span.order < best.span.order)
    ) {
      best = { span, score };
    }
  }

  return best?.span.milestoneId ?? null;
}

/**
 * 상세 진입 시 업무 선택 — 주차 → (분기 보기일 때) 분기 → 기준일과 가장 가까운 업무
 */
export function resolveTaskFocusMilestoneId(
  task: TaskWithMilestones,
  options: TaskFocusOptions,
): string | null {
  const weekFocus = resolveTaskWeekFocus(task, options.weekMonday);
  if (weekFocus) return weekFocus.milestoneId;

  if (!options.weekLensActive && options.quarter) {
    const quarterFocus = resolveTaskQuarterFocus(task, options.quarter);
    if (quarterFocus) return quarterFocus.milestoneId;
  }

  const refDate = options.weekLensActive ? options.weekMonday : new Date();
  return resolveTaskNearestMilestoneId(task, refDate);
}
