import type {
  TguPlanUi,
  TimelinePlanSegment,
  TimelineRange,
  TimelineRow,
  TimelineRowLayout
} from "../../model/timelineTypes";

export const TIMELINE_LABEL_WIDTH = 220;
export const TIMELINE_AXIS_HEIGHT = 46;
export const TIMELINE_BOTTOM_PADDING = 18;

/**
 * Вертикальная раскладка строки КА. Планы, которые не пересекаются по горизонтали, лежат на одной
 * дорожке (чистая цепочка — прямая линия). Если план пересёкся бы с уже размещённым, он уходит на
 * следующую дорожку (выше/ниже), чтобы планы не сливались. Высота строки растёт под самый глубокий
 * стек дорожек, а страйп ЗРВ опускается ниже планов.
 */
const PLAN_TOP_Y = 28; // центр верхней дорожки (lane 0) относительно верха строки
export const TIMELINE_LANE_STEP = 16; // шаг между дорожками пересекающихся планов
const ZRV_GAP = 16; // отступ от нижней дорожки планов до страйпа ЗРВ
const ZRV_BAND_MIN_Y = 52; // минимум: строка с одной дорожкой выглядит как раньше (высота 74)
export const TIMELINE_ZRV_BAND_HEIGHT = 14;
const ROW_BOTTOM_PAD = 8;
const SEGMENT_GAP_PX = 6; // минимальный горизонтальный зазор, иначе считаем планы пересекающимися

export function timeToX(timeMs: number, range: TimelineRange, timelineWidth: number): number {
  return ((timeMs - range.fromMs) / (range.toMs - range.fromMs)) * timelineWidth;
}

export function clipPlanToRange(plan: TguPlanUi, range: TimelineRange): { startMs: number; endMs: number } | null {
  if (plan.endMs < range.fromMs || plan.startMs > range.toMs) {
    return null;
  }

  return {
    startMs: Math.max(plan.startMs, range.fromMs),
    endMs: Math.min(plan.endMs, range.toMs)
  };
}

type PlacedPlan = { plan: TguPlanUi; x: number; x2: number; width: number };

/**
 * Жадная раскладка по дорожкам (interval graph): для каждого плана берём первую дорожку, чья правая
 * граница уже освободилась (с зазором SEGMENT_GAP_PX). Так пересекающиеся по горизонтали планы
 * гарантированно оказываются на разных дорожках. Планы должны быть отсортированы по x.
 */
function packLanes(items: PlacedPlan[]): number[] {
  const laneRightEdge: number[] = [];
  return items.map((item) => {
    let lane = laneRightEdge.findIndex((edge) => item.x >= edge + SEGMENT_GAP_PX);
    if (lane === -1) {
      lane = laneRightEdge.length;
    }
    laneRightEdge[lane] = item.x + item.width;
    return lane;
  });
}

function zrvBandYFor(rowTop: number, laneCount: number): number {
  return rowTop + Math.max(ZRV_BAND_MIN_Y, PLAN_TOP_Y + (laneCount - 1) * TIMELINE_LANE_STEP + ZRV_GAP);
}

export type TimelineLayout = {
  rowLayouts: TimelineRowLayout[];
  segments: TimelinePlanSegment[];
  height: number;
};

/**
 * Строит полную раскладку таймлайна: переменная высота строк (по числу дорожек), абсолютные
 * y-координаты планов и страйпов ЗРВ. Единственный источник правды по вертикали — компонент только
 * рисует по готовым координатам.
 */
export function buildTimelineLayout(
  rows: TimelineRow[],
  range: TimelineRange,
  timelineWidth: number
): TimelineLayout {
  const rowLayouts: TimelineRowLayout[] = [];
  const segments: TimelinePlanSegment[] = [];
  let cursorY = TIMELINE_AXIS_HEIGHT;

  rows.forEach((row, rowIndex) => {
    const placed: PlacedPlan[] = row.plans
      .map((plan) => ({ plan, clipped: clipPlanToRange(plan, range) }))
      .filter((item): item is { plan: TguPlanUi; clipped: { startMs: number; endMs: number } } => item.clipped !== null)
      .map(({ plan, clipped }) => {
        const x = timeToX(clipped.startMs, range, timelineWidth);
        const x2 = timeToX(clipped.endMs, range, timelineWidth);
        return { plan, x, x2, width: Math.max(3, x2 - x) };
      })
      .sort((a, b) => a.x - b.x || a.x2 - b.x2);

    const lanes = packLanes(placed);
    const laneCount = lanes.length === 0 ? 1 : Math.max(...lanes) + 1;
    const zrvBandY = zrvBandYFor(cursorY, laneCount);
    const height = zrvBandY - cursorY + TIMELINE_ZRV_BAND_HEIGHT + ROW_BOTTOM_PAD;

    placed.forEach((item, planIndex) => {
      segments.push({
        plan: item.plan,
        rowIndex,
        lane: lanes[planIndex],
        centerY: cursorY + PLAN_TOP_Y + lanes[planIndex] * TIMELINE_LANE_STEP,
        x: item.x,
        x2: item.x2,
        width: item.width
      });
    });

    rowLayouts.push({ row, rowIndex, y: cursorY, height, laneCount, zrvBandY });
    cursorY += height;
  });

  return { rowLayouts, segments, height: cursorY + TIMELINE_BOTTOM_PADDING };
}

export function buildTimeTicks(range: TimelineRange, targetCount = 8): number[] {
  const span = range.toMs - range.fromMs;
  const roughStep = span / targetCount;
  const candidates = [
    15 * 60_000,
    30 * 60_000,
    60 * 60_000,
    2 * 60 * 60_000,
    3 * 60 * 60_000,
    6 * 60 * 60_000,
    12 * 60 * 60_000,
    24 * 60 * 60_000
  ];
  const step = candidates.find((candidate) => candidate >= roughStep) ?? 24 * 60 * 60_000;
  const first = Math.ceil(range.fromMs / step) * step;
  const ticks: number[] = [];

  for (let tick = first; tick <= range.toMs; tick += step) {
    ticks.push(tick);
  }

  return ticks;
}
