import {
  applyVisualOverrides,
  type TimelineVisualOverrides,
} from "./allocationVisualState.js";
import type {
  TimelineAssignmentEntry,
  TimelineDemandEntry,
  useTimelineContext,
} from "./TimelineContext.js";
import { PROJECT_HEADER_HEIGHT, ROW_HEIGHT, SUB_LANE_HEIGHT } from "./timelineConstants.js";
import { getProjectRowMetricsKey } from "./timelineProjectMetrics.js";

export type TimelineProjectGroup = NonNullable<ReturnType<typeof useTimelineContext>["projectGroups"]>[number];

export type OpenDemandRowLayout = {
  visibleOpenDemands: TimelineDemandEntry[];
  laneMap: Map<string, number>;
  laneCount: number;
  rowHeight: number;
};

export type ProjectFlatRow =
  | {
      type: "header";
      key: string;
      project: TimelineProjectGroup;
    }
  | {
      type: "open-demand";
      key: string;
      projectId: string;
      openDemandCount: number;
      layout: OpenDemandRowLayout;
    }
  | {
      type: "resource";
      key: string;
      project: TimelineProjectGroup;
      resource: TimelineProjectGroup["resourceRows"][number]["resource"];
      allocs: TimelineAssignmentEntry[];
      metricsKey: string;
    };

export function estimateProjectRowHeight(row: ProjectFlatRow | undefined) {
  if (!row) return ROW_HEIGHT;
  if (row.type === "header") return PROJECT_HEADER_HEIGHT;
  if (row.type === "open-demand") return row.layout.rowHeight;
  return ROW_HEIGHT;
}

export function buildProjectFlatRows(
  visualProjectGroups: TimelineProjectGroup[],
  openDemandsByProject: Map<string, TimelineDemandEntry[]>,
  optimisticAllocations: TimelineVisualOverrides,
): ProjectFlatRow[] {
  const rows: ProjectFlatRow[] = [];

  for (const project of visualProjectGroups) {
    rows.push({ type: "header", key: `header-${project.id}`, project });

    const openDemands = openDemandsByProject.get(project.id) ?? [];
    if (openDemands.length > 0) {
      rows.push({
        type: "open-demand",
        key: `open-demand-${project.id}`,
        projectId: project.id,
        openDemandCount: openDemands.length,
        layout: buildOpenDemandRowLayout(openDemands, optimisticAllocations),
      });
    }

    for (const { resource, allocs } of project.resourceRows) {
      rows.push({
        type: "resource",
        key: `${project.id}-${resource.id}`,
        project,
        resource,
        allocs,
        metricsKey: getProjectRowMetricsKey(project.id, resource.id),
      });
    }
  }

  return rows;
}

function assignDemandLanes(demands: TimelineDemandEntry[]): Map<string, number> {
  const laneMap = new Map<string, number>();
  const laneEnds: Date[] = [];

  const sorted = [...demands].sort(
    (a, b) => new Date(a.startDate).getTime() - new Date(b.startDate).getTime(),
  );

  for (const demand of sorted) {
    const start = new Date(demand.startDate);
    let assigned = -1;
    for (let i = 0; i < laneEnds.length; i++) {
      if (laneEnds[i]! < start) {
        assigned = i;
        laneEnds[i] = new Date(demand.endDate);
        break;
      }
    }
    if (assigned === -1) {
      assigned = laneEnds.length;
      laneEnds.push(new Date(demand.endDate));
    }
    laneMap.set(demand.id, assigned);
  }

  return laneMap;
}

function buildOpenDemandRowLayout(
  openDemands: TimelineDemandEntry[],
  optimisticAllocations: TimelineVisualOverrides,
): OpenDemandRowLayout {
  const visibleOpenDemands = applyVisualOverrides(openDemands, optimisticAllocations);
  const laneMap = assignDemandLanes(visibleOpenDemands);
  const laneCount = laneMap.size > 0 ? Math.max(...laneMap.values()) + 1 : 1;

  return {
    visibleOpenDemands,
    laneMap,
    laneCount,
    rowHeight: Math.max(ROW_HEIGHT, laneCount * SUB_LANE_HEIGHT + 16),
  };
}
