import type { AppointmentRecord } from '@/types/appointment';

export type AppointmentTimedInterval = {
  id: string;
  start: number;
  end: number;
};

export type AppointmentLaneLayout = {
  lane: number;
  /** Max concurrent overlaps in this appointment's cluster (column count). */
  laneCount: number;
};

function intervalsOverlap(a: AppointmentTimedInterval, b: AppointmentTimedInterval): boolean {
  return a.start < b.end && b.start < a.end;
}

export function toTimedInterval(apt: AppointmentRecord): AppointmentTimedInterval {
  return {
    id: apt.id,
    start: new Date(apt.startAt).getTime(),
    end: new Date(apt.endAt).getTime(),
  };
}

/** Connected overlap component containing `appointmentId`. */
export function findOverlapCluster(
  appointmentId: string,
  appointments: AppointmentRecord[],
): AppointmentRecord[] {
  const byId = new Map(appointments.map((a) => [a.id, a]));
  if (!byId.has(appointmentId)) {
    return [];
  }

  const timed = appointments.map(toTimedInterval);
  const clusterIds = new Set<string>([appointmentId]);
  let changed = true;

  while (changed) {
    changed = false;
    for (const interval of timed) {
      if (clusterIds.has(interval.id)) {
        continue;
      }
      for (const memberId of clusterIds) {
        const member = timed.find((t) => t.id === memberId);
        if (member && intervalsOverlap(interval, member)) {
          clusterIds.add(interval.id);
          changed = true;
          break;
        }
      }
    }
  }

  return appointments.filter((a) => clusterIds.has(a.id));
}

function maxConcurrentCount(intervals: AppointmentTimedInterval[]): number {
  if (intervals.length === 0) {
    return 0;
  }

  type Point = { time: number; delta: number };
  const points: Point[] = [];
  for (const interval of intervals) {
    points.push({ time: interval.start, delta: 1 });
    points.push({ time: interval.end, delta: -1 });
  }
  points.sort((a, b) => a.time - b.time || a.delta - b.delta);

  let current = 0;
  let max = 0;
  for (const point of points) {
    current += point.delta;
    max = Math.max(max, current);
  }
  return max;
}

function assignGreedyLanes(intervals: AppointmentTimedInterval[]): Map<string, number> {
  const sorted = [...intervals].sort((a, b) => a.start - b.start || a.end - b.end);
  const laneEndTimes: number[] = [];
  const laneById = new Map<string, number>();

  for (const interval of sorted) {
    let lane = laneEndTimes.findIndex((end) => end <= interval.start);
    if (lane === -1) {
      lane = laneEndTimes.length;
      laneEndTimes.push(interval.end);
    } else {
      laneEndTimes[lane] = interval.end;
    }
    laneById.set(interval.id, lane);
  }

  return laneById;
}

function buildClusters(intervals: AppointmentTimedInterval[]): AppointmentTimedInterval[][] {
  const visited = new Set<string>();
  const clusters: AppointmentTimedInterval[][] = [];

  for (const seed of intervals) {
    if (visited.has(seed.id)) {
      continue;
    }
    const cluster: AppointmentTimedInterval[] = [];
    const queue = [seed];
    visited.add(seed.id);
    while (queue.length > 0) {
      const current = queue.pop()!;
      cluster.push(current);
      for (const other of intervals) {
        if (!visited.has(other.id) && intervalsOverlap(current, other)) {
          visited.add(other.id);
          queue.push(other);
        }
      }
    }
    clusters.push(cluster);
  }

  return clusters;
}

/**
 * Assigns side-by-side lanes per provider column (Google Calendar style).
 */
export function computeAppointmentLaneLayouts(
  appointments: AppointmentRecord[],
): Map<string, AppointmentLaneLayout> {
  const timed = appointments.map(toTimedInterval);
  if (timed.length === 0) {
    return new Map();
  }

  const layouts = new Map<string, AppointmentLaneLayout>();
  const clusters = buildClusters(timed);

  for (const cluster of clusters) {
    const laneCount = Math.max(1, maxConcurrentCount(cluster));
    const greedyLanes = assignGreedyLanes(cluster);
    const usedLaneIndices = [...new Set(cluster.map((c) => greedyLanes.get(c.id) ?? 0))].sort(
      (a, b) => a - b,
    );
    const remap = new Map(usedLaneIndices.map((lane, index) => [lane, index]));

    for (const interval of cluster) {
      const rawLane = greedyLanes.get(interval.id) ?? 0;
      layouts.set(interval.id, {
        lane: remap.get(rawLane) ?? 0,
        laneCount,
      });
    }
  }

  return layouts;
}

export function lanePositionStyles(lane: number, laneCount: number): {
  left: string;
  width: string;
} {
  const gapPct = 1;
  const widthPct = (100 - gapPct * (laneCount + 1)) / laneCount;
  return {
    left: `calc(${gapPct}% + ${lane} * (${widthPct}% + ${gapPct}%))`,
    width: `${widthPct}%`,
  };
}
