import type {
  CreateTruckLaneScheduleRequest,
  TruckLaneMoveTargetRequest,
  TruckLaneScheduleLineRequest,
} from "@/app/api/shop/actions";
import { movesToScheduleLines } from "@/components/Shop/scheduleLineAdapter";
import { normalizeStoreId } from "@/app/utils/formatUtil";
import type {
  ScheduleLaneOption,
  ScheduleMoveSelection,
} from "@/components/Shop/ScheduleChangeModal";
import type {
  PlannedLane,
  PlannedShop,
  ScheduleModification,
  ScheduleDragWorkspaceState,
  ScheduledDeleteSnapshot,
} from "@/components/Shop/scheduleDragWorkspace";
import {
  listModifications,
  normalizeDepartureTime,
} from "@/components/Shop/scheduleDragWorkspace";

function plannedLaneById(
  plannedLanes: ScheduleDragWorkspaceState | undefined,
): Map<string, PlannedLane> {
  return new Map((plannedLanes ?? []).map((l) => [l.id, l]));
}

function departureTimeForPlannedLane(
  laneId: string,
  plannedLanes: ScheduleDragWorkspaceState | undefined,
): string | null {
  const planned = plannedLaneById(plannedLanes).get(laneId);
  if (!planned) return null;
  if (
    normalizeDepartureTime(planned.departureTime) ===
    normalizeDepartureTime(planned.originalDepartureTime)
  ) {
    return null;
  }
  return normalizeDepartureTime(planned.departureTime);
}

export function buildScheduleMoveFromSelection(
  truckRowId: number,
  selection: ScheduleMoveSelection,
  lane: ScheduleLaneOption,
  toDistrictReference?: string | null,
  plannedLanes?: ScheduleDragWorkspaceState,
): TruckLaneMoveTargetRequest {
  return {
    truckRowId,
    toTruckLanceCode: lane.truckLanceCode,
    toRemark:
      lane.remark != null && String(lane.remark).trim() !== ""
        ? String(lane.remark).trim()
        : null,
    toStoreId: normalizeStoreId(lane.storeId),
    toLoadingSequence: selection.toLoadingSequence,
    toDistrictReference: toDistrictReference ?? null,
    departureTime: departureTimeForPlannedLane(selection.laneId, plannedLanes),
  };
}

export function buildCreateScheduleLine(
  shop: PlannedShop,
  lane: ScheduleLaneOption,
): TruckLaneScheduleLineRequest {
  return {
    action: "CREATE",
    shopId: shop.shopEntityId ?? null,
    shopCode: shop.shopCode,
    shopName: shop.displayName,
    toTruckLanceCode: lane.truckLanceCode,
    toRemark:
      lane.remark != null && String(lane.remark).trim() !== ""
        ? String(lane.remark).trim()
        : null,
    toStoreId: normalizeStoreId(lane.storeId),
    toLoadingSequence: shop.loadingSequence,
    toDistrictReference: shop.districtReferenceRaw ?? null,
    departureTime: lane.departureTime || "00:00:00",
  };
}

export function buildDeleteScheduleLine(
  truckRowId: number,
  lane: ScheduleLaneOption,
  shopCode: string,
  shopName: string,
): TruckLaneScheduleLineRequest {
  return {
    action: "DELETE",
    truckRowId,
    toTruckLanceCode: lane.truckLanceCode,
    toRemark:
      lane.remark != null && String(lane.remark).trim() !== ""
        ? String(lane.remark).trim()
        : null,
    toStoreId: normalizeStoreId(lane.storeId),
    shopCode,
    shopName,
  };
}

function districtByTruckRowIdFromPlanned(
  plannedLanes?: ScheduleDragWorkspaceState,
): Map<number, string | null> {
  const map = new Map<number, string | null>();
  if (!plannedLanes) return map;
  for (const lane of plannedLanes) {
    for (const shop of lane.shops) {
      map.set(shop.truckRowId, shop.districtReferenceRaw);
    }
  }
  return map;
}

export function buildScheduleMovesFromMappings(
  mappings: Record<number, ScheduleMoveSelection>,
  lanes: ScheduleLaneOption[],
  plannedLanes?: ScheduleDragWorkspaceState,
): TruckLaneMoveTargetRequest[] {
  const laneById = new Map(lanes.map((l) => [l.id, l]));
  const districtByTruckRowId = districtByTruckRowIdFromPlanned(plannedLanes);
  const out: TruckLaneMoveTargetRequest[] = [];
  for (const [id, sel] of Object.entries(mappings)) {
    const truckRowId = Number(id);
    const lane = laneById.get(sel.laneId);
    if (!lane || !Number.isFinite(truckRowId)) continue;
    const seq = sel.toLoadingSequence;
    if (!Number.isFinite(seq) || seq < 0) continue;
    out.push(
      buildScheduleMoveFromSelection(
        truckRowId,
        sel,
        lane,
        districtByTruckRowId.get(truckRowId),
        plannedLanes,
      ),
    );
  }
  return out;
}

function buildDepartureOnlyMoves(
  plannedLanes: ScheduleDragWorkspaceState,
  pendingDeleteIds: Set<number>,
): TruckLaneMoveTargetRequest[] {
  const modifiedIds = new Set(
    listModifications(plannedLanes).map((m) => m.truckRowId),
  );
  const out: TruckLaneMoveTargetRequest[] = [];
  for (const lane of plannedLanes) {
    const departureTime = departureTimeForPlannedLane(lane.id, plannedLanes);
    if (!departureTime) continue;
    const laneOpt: ScheduleLaneOption = {
      id: lane.id,
      label: lane.label,
      truckLanceCode: lane.truckLanceCode,
      remark: lane.remark,
      storeId: lane.storeId,
      departureTime: lane.departureTime,
      shops: [],
    };
    for (const shop of lane.shops) {
      if (shop.truckRowId < 0) continue;
      if (pendingDeleteIds.has(shop.truckRowId)) continue;
      if (modifiedIds.has(shop.truckRowId)) continue;
      out.push(
        buildScheduleMoveFromSelection(
          shop.truckRowId,
          { laneId: lane.id, toLoadingSequence: shop.loadingSequence },
          laneOpt,
          shop.districtReferenceRaw,
          plannedLanes,
        ),
      );
    }
  }
  return out;
}

export function buildScheduleLinesFromPlan(input: {
  modifications: ScheduleModification[];
  pendingDeletes: ScheduledDeleteSnapshot[];
  lanes: ScheduleLaneOption[];
  plannedLanes?: ScheduleDragWorkspaceState;
}): TruckLaneScheduleLineRequest[] {
  const laneById = new Map(input.lanes.map((l) => [l.id, l]));
  const deleteIds = new Set(input.pendingDeletes.map((d) => d.truckRowId));
  const mappings = Object.fromEntries(
    input.modifications
      .filter((m) => !deleteIds.has(m.truckRowId))
      .map((m) => [
        m.truckRowId,
        { laneId: m.toLaneId, toLoadingSequence: m.newLoadingSequence },
      ]),
  );
  const moveTargets = [
    ...buildScheduleMovesFromMappings(
      mappings,
      input.lanes,
      input.plannedLanes,
    ),
    ...(input.plannedLanes
      ? buildDepartureOnlyMoves(input.plannedLanes, deleteIds)
      : []),
  ];
  const moveLines = movesToScheduleLines(moveTargets);
  const deleteLines = input.pendingDeletes
    .map((d) => {
      const lane = laneById.get(d.fromLaneId);
      if (!lane) return null;
      return buildDeleteScheduleLine(
        d.truckRowId,
        lane,
        d.shopCode,
        d.displayName,
      );
    })
    .filter((line): line is TruckLaneScheduleLineRequest => line != null);
  const createLines: TruckLaneScheduleLineRequest[] = [];
  if (input.plannedLanes) {
    for (const lane of input.plannedLanes) {
      const laneOpt = laneById.get(lane.id);
      if (!laneOpt) continue;
      const effectiveLane: ScheduleLaneOption = {
        ...laneOpt,
        departureTime: lane.departureTime || laneOpt.departureTime,
      };
      for (const shop of lane.shops) {
        if (shop.truckRowId >= 0) continue;
        if (!shop.shopEntityId || shop.shopEntityId <= 0) continue;
        createLines.push(buildCreateScheduleLine(shop, effectiveLane));
      }
    }
  }
  return [...moveLines, ...deleteLines, ...createLines];
}

export function buildScheduleMovesFromModifications(
  modifications: ScheduleModification[],
  lanes: ScheduleLaneOption[],
  plannedLanes?: ScheduleDragWorkspaceState,
  pendingDeletes: ScheduledDeleteSnapshot[] = [],
): TruckLaneMoveTargetRequest[] {
  const deleteIds = new Set(pendingDeletes.map((d) => d.truckRowId));
  const mappings = Object.fromEntries(
    modifications
      .filter((m) => !deleteIds.has(m.truckRowId))
      .map((m) => [
        m.truckRowId,
        { laneId: m.toLaneId, toLoadingSequence: m.newLoadingSequence },
      ]),
  );
  return buildScheduleMovesFromMappings(mappings, lanes, plannedLanes);
}

export function buildCreateScheduleRequest(input: {
  executeAt: string;
  note?: string | null;
  mappings: Record<number, ScheduleMoveSelection>;
  lanes: ScheduleLaneOption[];
  plannedLanes?: ScheduleDragWorkspaceState;
  pendingDeletes?: ScheduledDeleteSnapshot[];
  modifications?: ScheduleModification[];
}): CreateTruckLaneScheduleRequest {
  if (input.modifications != null) {
    return {
      executeAt: input.executeAt,
      note: input.note ?? null,
      lines: buildScheduleLinesFromPlan({
        modifications: input.modifications,
        pendingDeletes: input.pendingDeletes ?? [],
        lanes: input.lanes,
        plannedLanes: input.plannedLanes,
      }),
    };
  }
  const moves = buildScheduleMovesFromMappings(
    input.mappings,
    input.lanes,
    input.plannedLanes,
  );
  const deleteLines = (input.pendingDeletes ?? [])
    .map((d) => {
      const lane = input.lanes.find((l) => l.id === d.fromLaneId);
      if (!lane) return null;
      return buildDeleteScheduleLine(
        d.truckRowId,
        lane,
        d.shopCode,
        d.displayName,
      );
    })
    .filter((line): line is TruckLaneScheduleLineRequest => line != null);
  return {
    executeAt: input.executeAt,
    note: input.note ?? null,
    lines: [...movesToScheduleLines(moves), ...deleteLines],
  };
}

export function buildCreateScheduleRequestFromModifications(input: {
  executeAt: string;
  note?: string | null;
  modifications: ScheduleModification[];
  lanes: ScheduleLaneOption[];
  plannedLanes?: ScheduleDragWorkspaceState;
  pendingDeletes?: ScheduledDeleteSnapshot[];
}): CreateTruckLaneScheduleRequest {
  return {
    executeAt: input.executeAt,
    note: input.note ?? null,
    lines: buildScheduleLinesFromPlan({
      modifications: input.modifications,
      pendingDeletes: input.pendingDeletes ?? [],
      lanes: input.lanes,
      plannedLanes: input.plannedLanes,
    }),
  };
}
