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

export type PlannedShop = {
  id: number;
  truckRowId: number;
  shopEntityId?: 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;
  /** Lane departure (HH:mm or HH:mm:ss). */
  departureTime: string;
  originalDepartureTime: string;
  shops: PlannedShop[];
};

export type ScheduleDepartureModification = {
  laneId: string;
  laneLabel: string;
  oldDepartureTime: string;
  newDepartureTime: string;
  shopCount: number;
  pendingCreateCount: number;
};

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;
};

export type SchedulePendingCreate = {
  truckRowId: number;
  shopEntityId: number;
  shopCode: string;
  displayName: string;
  location: string;
  toLaneId: string;
  toLaneLabel: string;
};

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,
  );
}

/** Parse user-entered departure time; returns null when empty or invalid. */
export function parseScheduleDepartureTime(time: string): string | null {
  const s = String(time ?? "").trim();
  if (!s) return null;
  if (!/^\d{1,2}:\d{2}(:\d{2})?$/.test(s)) return null;
  const [hh, mm, ss] = s.split(":");
  const h = Number(hh);
  const m = Number(mm);
  if (!Number.isFinite(h) || !Number.isFinite(m) || h > 23 || m > 59) {
    return null;
  }
  if (ss != null) {
    const sec = Number(ss);
    if (!Number.isFinite(sec) || sec > 59) return null;
  }
  return normalizeDepartureTime(s);
}

export function normalizeDepartureTime(time: string): string {
  const s = String(time ?? "").trim();
  if (!s) return "00:00:00";
  if (/^\d{1,2}:\d{2}(:\d{2})?$/.test(s)) {
    const [hh, mm, ss] = s.split(":");
    return `${String(hh).padStart(2, "0")}:${String(mm).padStart(2, "0")}${
      ss != null ? `:${String(ss).padStart(2, "0")}` : ":00"
    }`;
  }
  return s;
}

export function plannedLanesToWarningInputRows(
  state: ScheduleDragWorkspaceState,
): TruckLaneWarningInputRow[] {
  const out: TruckLaneWarningInputRow[] = [];
  for (const lane of state) {
    for (const shop of lane.shops) {
      out.push({
        truckRowId: shop.truckRowId,
        truckLanceCode: lane.truckLanceCode,
        laneRemark: lane.remark ?? null,
        storeId: shop.storeId,
        departureTime: lane.departureTime,
        shopEntityId: shop.shopEntityId ?? null,
        shopCode: shop.shopCode,
        shopDisplayName: shop.branchName || shop.displayName,
      });
    }
  }
  return out;
}

function departureTimesEqual(a: string, b: string): boolean {
  return normalizeDepartureTime(a) === normalizeDepartureTime(b);
}

function planHasShopEntity(
  state: ScheduleDragWorkspaceState,
  shopEntityId: number,
  shopCode: string,
  excludeTruckRowId = -1,
): boolean {
  const code = shopCode.trim().toLowerCase();
  for (const lane of state) {
    for (const shop of lane.shops) {
      if (shop.truckRowId === excludeTruckRowId) continue;
      if (shop.shopEntityId === shopEntityId) return true;
      if (code && shop.shopCode.trim().toLowerCase() === code) return true;
    }
  }
  return false;
}

export function addShopToPlan(
  state: ScheduleDragWorkspaceState,
  laneId: string,
  pick: { id: number; name: string; code: string },
  tempTruckRowId: number,
): ScheduleDragWorkspaceState {
  const lane = state.find((l) => l.id === laneId);
  if (!lane) return state;

  const shopCode = String(pick.code || "").trim();
  const shopName = String(pick.name || "").trim();
  if (!shopCode || !shopName || pick.id <= 0) return state;
  if (planHasShopEntity(state, pick.id, shopCode)) return state;

  const flat = flattenDisplayOrder(lane.shops);
  const last = flat[flat.length - 1];
  const seq = last != null ? Number(last.loadingSequence) || 0 : 0;

  const newShop: PlannedShop = {
    id: tempTruckRowId,
    truckRowId: tempTruckRowId,
    shopEntityId: pick.id,
    shopCode,
    displayName: `${shopCode} - ${shopName}`,
    branchName: shopName,
    storeId: lane.storeId,
    districtReferenceRaw: null,
    loadingSequence: seq,
    originalLaneId: laneId,
    originalLoadingSequence: seq,
    originalDistrictReferenceRaw: null,
  };

  return state.map((l) =>
    l.id !== laneId ? l : { ...l, shops: [...l.shops, newShop] },
  );
}

export function listPendingCreates(
  state: ScheduleDragWorkspaceState,
): SchedulePendingCreate[] {
  const out: SchedulePendingCreate[] = [];
  for (const lane of state) {
    for (const shop of lane.shops) {
      if (shop.truckRowId >= 0) continue;
      if (!shop.shopEntityId || shop.shopEntityId <= 0) continue;
      out.push({
        truckRowId: shop.truckRowId,
        shopEntityId: shop.shopEntityId,
        shopCode: shop.shopCode,
        displayName: shop.displayName,
        location: formatPlannedShopLocation(shop),
        toLaneId: lane.id,
        toLaneLabel: lane.label,
      });
    }
  }
  return out.sort((a, b) => a.shopCode.localeCompare(b.shopCode, "zh-Hant"));
}

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);

    const departureTime = normalizeDepartureTime(lane.departureTime || "00:00:00");
    return {
      id: lane.id,
      label: lane.label,
      truckLanceCode: lane.truckLanceCode,
      remark: lane.remark,
      storeId: lane.storeId,
      departureTime,
      originalDepartureTime: departureTime,
      shops: plannedShops,
    };
  });
}

export function setLaneDepartureTime(
  state: ScheduleDragWorkspaceState,
  laneId: string,
  departureTime: string,
): ScheduleDragWorkspaceState {
  const normalized = normalizeDepartureTime(departureTime);
  return state.map((lane) =>
    lane.id !== laneId ? lane : { ...lane, departureTime: normalized },
  );
}

export function revertLaneDeparture(
  state: ScheduleDragWorkspaceState,
  laneId: string,
): ScheduleDragWorkspaceState {
  return state.map((lane) =>
    lane.id !== laneId
      ? lane
      : { ...lane, departureTime: lane.originalDepartureTime },
  );
}

export function listDepartureModifications(
  state: ScheduleDragWorkspaceState,
): ScheduleDepartureModification[] {
  return state
    .filter((lane) => !departureTimesEqual(lane.departureTime, lane.originalDepartureTime))
    .map((lane) => ({
      laneId: lane.id,
      laneLabel: lane.label,
      oldDepartureTime: lane.originalDepartureTime,
      newDepartureTime: lane.departureTime,
      shopCount: lane.shops.filter((s) => s.truckRowId >= 0).length,
      pendingCreateCount: lane.shops.filter((s) => s.truckRowId < 0).length,
    }))
    .filter((d) => d.shopCount > 0 || d.pendingCreateCount > 0);
}

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) {
      if (shop.truckRowId < 0) continue;
      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] ?? "",
  };
}
