import type {
  ScheduleLaneOption,
  ScheduleMoveSelection,
  ScheduleShopRow,
} from "@/components/Shop/ScheduleChangeModal";
import {
  computeMovedLoadingSequence,
  flattenDisplayOrder,
  formatPlannedShopLocation,
  toDistrictDisplayName,
  toDistrictRawValue,
} from "@/components/Shop/routeBoardDisplayOrder";

export type PlannedShop = {
  id: number;
  truckRowId: number;
  shopCode: string;
  displayName: string;
  branchName: string;
  storeId: string;
  districtReferenceRaw: string | null;
  loadingSequence: number;
  originalLaneId: string;
  originalLoadingSequence: number;
  originalDistrictReferenceRaw: string | null;
};

export type PlannedLane = {
  id: string;
  label: string;
  truckLanceCode: string;
  remark?: string | null;
  storeId: string;
  shops: PlannedShop[];
};

export type ScheduleDragWorkspaceState = PlannedLane[];

export type ScheduleModification = {
  truckRowId: number;
  shopCode: string;
  displayName: string;
  location: string;
  fromLaneId: string;
  fromLaneLabel: string;
  toLaneId: string;
  toLaneLabel: string;
  oldLoadingSequence: number;
  newLoadingSequence: number;
  isLaneChanged: boolean;
  isSeqChanged: boolean;
  isDistrictChanged: boolean;
  oldDistrictLabel: string;
  newDistrictLabel: string;
};

export type ScheduledDeleteSnapshot = {
  truckRowId: number;
  shopCode: string;
  displayName: string;
  location: string;
  fromLaneId: string;
  fromLaneLabel: string;
  shop: PlannedShop;
};

type MoveShopArgs = {
  shopId: number;
  fromLaneId: string;
  toLaneId: string;
  beforeShopId?: number | null;
  targetDistrict?: string | null;
};

function laneHasShopCode(lane: PlannedLane, shopCode: string, excludeId: number): boolean {
  return lane.shops.some(
    (s) => s.truckRowId !== excludeId && s.shopCode === shopCode,
  );
}

export function initPlannedLanes(
  lanes: ScheduleLaneOption[],
  shops: ScheduleShopRow[],
): ScheduleDragWorkspaceState {
  const shopRowById = new Map(shops.map((s) => [s.truckRowId, s]));

  return lanes.map((lane) => {
    const plannedShops: PlannedShop[] = lane.shops
      .map((snap) => {
        const row = shopRowById.get(snap.truckRowId);
        if (!row) return null;
        const seq = snap.loadingSequence ?? 0;
        return {
          id: row.truckRowId,
          truckRowId: row.truckRowId,
          shopCode: row.shopCode,
          displayName: row.displayName,
          branchName: row.branchName,
          storeId: row.storeId,
          districtReferenceRaw: row.districtReferenceRaw,
          loadingSequence: seq,
          originalLaneId: row.currentLaneId,
          originalLoadingSequence: seq,
          originalDistrictReferenceRaw: row.districtReferenceRaw,
        } satisfies PlannedShop;
      })
      .filter((s): s is PlannedShop => s != null);

    return {
      id: lane.id,
      label: lane.label,
      truckLanceCode: lane.truckLanceCode,
      remark: lane.remark,
      storeId: lane.storeId,
      shops: plannedShops,
    };
  });
}

export function moveShop(
  state: ScheduleDragWorkspaceState,
  args: MoveShopArgs,
): ScheduleDragWorkspaceState {
  const { shopId, fromLaneId, toLaneId, beforeShopId = null, targetDistrict } =
    args;

  if (beforeShopId != null && beforeShopId === shopId) {
    return state;
  }

  const next = state.map((lane) => ({ ...lane, shops: lane.shops.slice() }));
  const from = next.find((l) => l.id === fromLaneId);
  const to = next.find((l) => l.id === toLaneId);
  if (!from || !to) return state;

  const shop = from.shops.find((s) => s.truckRowId === shopId);
  if (!shop) return state;

  if (from.id !== to.id && laneHasShopCode(to, shop.shopCode, shop.truckRowId)) {
    return state;
  }

  const fromFlat = flattenDisplayOrder(
    from.shops.filter((s) => s.truckRowId !== shopId),
  );
  const toFlatRaw = flattenDisplayOrder(to.shops);
  const toFlat =
    from.id === to.id
      ? toFlatRaw.filter((s) => s.truckRowId !== shopId)
      : toFlatRaw.slice();

  const beforeShop =
    beforeShopId != null
      ? toFlat.find((s) => s.truckRowId === beforeShopId)
      : null;
  const isCrossLane = from.id !== to.id;
  const targetDistrictRaw =
    targetDistrict !== undefined
      ? toDistrictRawValue(targetDistrict)
      : isCrossLane
        ? shop.districtReferenceRaw
        : beforeShop
          ? toDistrictRawValue(beforeShop.districtReferenceRaw)
          : shop.districtReferenceRaw;

  const moved: PlannedShop = {
    ...shop,
    districtReferenceRaw: targetDistrictRaw,
  };

  const insertIdx =
    beforeShopId != null
      ? toFlat.findIndex((s) => s.truckRowId === beforeShopId)
      : targetDistrict !== undefined
        ? (() => {
            const targetDisplay = toDistrictDisplayName(targetDistrictRaw);
            for (let i = toFlat.length - 1; i >= 0; i -= 1) {
              if (
                toDistrictDisplayName(toFlat[i].districtReferenceRaw) ===
                targetDisplay
              ) {
                return i + 1;
              }
            }
            return -1;
          })()
        : -1;

  const inserted =
    insertIdx >= 0
      ? [...toFlat.slice(0, insertIdx), moved, ...toFlat.slice(insertIdx)]
      : [...toFlat, moved];

  const newSeq = computeMovedLoadingSequence(
    inserted,
    moved.truckRowId,
    targetDistrict !== undefined && beforeShopId == null,
  );

  from.shops = fromFlat;
  to.shops = inserted.map((s) =>
    s.truckRowId === moved.truckRowId ? { ...s, loadingSequence: newSeq } : s,
  );

  return next;
}

export function setShopLoadingSequence(
  state: ScheduleDragWorkspaceState,
  laneId: string,
  shopId: number,
  loadingSequence: number,
): ScheduleDragWorkspaceState {
  const seq = Math.max(0, Math.trunc(loadingSequence));
  const lane = state.find((l) => l.id === laneId);
  const shop = lane?.shops.find((s) => s.truckRowId === shopId);
  if (!shop || shop.loadingSequence === seq) return state;

  return state.map((l) =>
    l.id !== laneId
      ? l
      : {
          ...l,
          shops: l.shops.map((s) =>
            s.truckRowId === shopId ? { ...s, loadingSequence: seq } : s,
          ),
        },
  );
}

export function revertShop(
  state: ScheduleDragWorkspaceState,
  shopId: number,
  currentLaneId: string,
): ScheduleDragWorkspaceState {
  const currentLane = state.find((l) => l.id === currentLaneId);
  const shop = currentLane?.shops.find((s) => s.truckRowId === shopId);
  if (!shop) return state;

  let next = moveShop(state, {
    shopId,
    fromLaneId: currentLaneId,
    toLaneId: shop.originalLaneId,
    beforeShopId: null,
  });

  next = next.map((lane) => {
    if (lane.id !== shop.originalLaneId) return lane;
    return {
      ...lane,
      shops: lane.shops.map((s) =>
        s.truckRowId === shopId
          ? {
              ...s,
              loadingSequence: shop.originalLoadingSequence,
              districtReferenceRaw: shop.originalDistrictReferenceRaw,
            }
          : s,
      ),
    };
  });

  return next;
}

export function findShopLaneId(
  state: ScheduleDragWorkspaceState,
  shopId: number,
): string | null {
  for (const lane of state) {
    if (lane.shops.some((s) => s.truckRowId === shopId)) return lane.id;
  }
  return null;
}

export function removeShopFromPlan(
  state: ScheduleDragWorkspaceState,
  truckRowId: number,
): {
  next: ScheduleDragWorkspaceState;
  removed: PlannedShop | null;
  laneId: string | null;
} {
  for (const lane of state) {
    const shop = lane.shops.find((s) => s.truckRowId === truckRowId);
    if (!shop) continue;
    return {
      next: state.map((l) =>
        l.id === lane.id
          ? { ...l, shops: l.shops.filter((s) => s.truckRowId !== truckRowId) }
          : l,
      ),
      removed: shop,
      laneId: lane.id,
    };
  }
  return { next: state, removed: null, laneId: null };
}

export function restoreDeletedShop(
  state: ScheduleDragWorkspaceState,
  snapshot: ScheduledDeleteSnapshot,
): ScheduleDragWorkspaceState {
  const lane = state.find((l) => l.id === snapshot.fromLaneId);
  if (!lane) return state;
  if (lane.shops.some((s) => s.truckRowId === snapshot.truckRowId)) return state;

  const shop: PlannedShop = {
    ...snapshot.shop,
    loadingSequence: snapshot.shop.originalLoadingSequence,
    districtReferenceRaw: snapshot.shop.originalDistrictReferenceRaw,
  };

  return state.map((l) =>
    l.id === snapshot.fromLaneId ? { ...l, shops: [...l.shops, shop] } : l,
  );
}

export function listModifications(
  state: ScheduleDragWorkspaceState,
): ScheduleModification[] {
  const laneById = new Map(state.map((l) => [l.id, l]));
  const out: ScheduleModification[] = [];

  for (const lane of state) {
    for (const shop of lane.shops) {
      const isLaneChanged = shop.originalLaneId !== lane.id;
      const isSeqChanged = shop.originalLoadingSequence !== shop.loadingSequence;
      const isDistrictChanged =
        (shop.originalDistrictReferenceRaw ?? "") !==
        (shop.districtReferenceRaw ?? "");
      if (!isLaneChanged && !isSeqChanged && !isDistrictChanged) continue;

      const fromLane = laneById.get(shop.originalLaneId);
      out.push({
        truckRowId: shop.truckRowId,
        shopCode: shop.shopCode,
        displayName: shop.displayName,
        location: formatPlannedShopLocation(shop),
        fromLaneId: shop.originalLaneId,
        fromLaneLabel: fromLane?.label ?? shop.originalLaneId,
        toLaneId: lane.id,
        toLaneLabel: lane.label,
        oldLoadingSequence: shop.originalLoadingSequence,
        newLoadingSequence: shop.loadingSequence,
        isLaneChanged,
        isSeqChanged,
        isDistrictChanged,
        oldDistrictLabel: toDistrictDisplayName(shop.originalDistrictReferenceRaw),
        newDistrictLabel: toDistrictDisplayName(shop.districtReferenceRaw),
      });
    }
  }

  return out.sort((a, b) => a.shopCode.localeCompare(b.shopCode, "zh-Hant"));
}

export function toScheduleMappings(
  modifications: ScheduleModification[],
  excludeTruckRowIds: Set<number> = new Set(),
): Record<number, ScheduleMoveSelection> {
  const out: Record<number, ScheduleMoveSelection> = {};
  for (const mod of modifications) {
    if (excludeTruckRowIds.has(mod.truckRowId)) continue;
    out[mod.truckRowId] = {
      laneId: mod.toLaneId,
      toLoadingSequence: mod.newLoadingSequence,
    };
  }
  return out;
}

export function defaultFocusedLaneIds(
  lanes: ScheduleLaneOption[],
): { leftLaneId: string; rightLaneId: string } {
  const ids = lanes.map((l) => l.id);
  return {
    leftLaneId: ids[0] ?? "",
    rightLaneId: ids[1] ?? ids[0] ?? "",
  };
}
