import type { TguPlan, TguPlatform } from "../../model/tguTypes";
import type { TguPlanUi, TguPlatformUi, TimelineRange, TimelineRow } from "../../model/timelineTypes";
import { parseLocal } from "../../utils/localTime";

const PROBLEM_STATUSES = new Set(["REJECTED", "START_AMBIGUOUS"]);

const OPERATIONAL_STATUS = "OPERATIONAL";

/** На морде показываем только эксплуатируемые КА — с остальными мы не работаем. */
export function isOperationalPlatform(platform: TguPlatform): boolean {
  return (platform.status ?? "").trim().toUpperCase() === OPERATIONAL_STATUS;
}

/** norad-id эксплуатируемых КА; planId.spacecraftId сопоставляется именно с norad-id. */
export function operationalSpacecraftIds(platforms: TguPlatform[]): Set<string> {
  const ids = new Set<string>();
  for (const platform of platforms) {
    if (platform.noradId !== null && platform.noradId !== undefined) {
      ids.add(String(platform.noradId));
    }
  }
  return ids;
}

export function mapPlans(plans: TguPlan[], platforms: TguPlatform[]): TguPlanUi[] {
  const platformByNorad = buildPlatformByNorad(platforms);

  return plans
    .map((plan) => {
      const startMs = parseLocal(plan.startTime);
      const endMs = parseLocal(plan.endTime);
      const platform = platformByNorad.get(plan.spacecraftId);
      return {
        ...plan,
        startMs,
        endMs,
        platform: platform ? toPlatformUi(plan.spacecraftId, platform, 0, false) : undefined
      };
    })
    .filter((plan) => Number.isFinite(plan.startMs) && Number.isFinite(plan.endMs));
}

export function buildPlatformsForInterval(
  platforms: TguPlatform[],
  plans: TguPlanUi[],
  range: TimelineRange
): TguPlatformUi[] {
  const intervalPlans = filterPlansByRange(plans, range);
  const platformByNorad = buildPlatformByNorad(platforms);

  // Красный индикатор КА = есть НЕразрешённый проблемный план (см. unresolvedProblemSpacecraftIds):
  // отказ, который оператор ещё не обработал (цепочка не продвинулась) и не снял с учёта. Прежняя
  // логика «есть хоть один REJECTED» красила КА вечно — даже после исправления или снятия с учёта.
  const problemSpacecraft = unresolvedProblemSpacecraftIds(intervalPlans);
  const planCount = new Map<string, number>();
  for (const plan of intervalPlans) {
    planCount.set(plan.spacecraftId, (planCount.get(plan.spacecraftId) ?? 0) + 1);
  }

  const platformItems = Array.from(platformByNorad.entries()).map(([spacecraftId, platform]) =>
    toPlatformUi(spacecraftId, platform, planCount.get(spacecraftId) ?? 0, problemSpacecraft.has(spacecraftId))
  );

  const knownSpacecraft = new Set(platformItems.map((platform) => platform.spacecraftId));
  for (const [spacecraftId, count] of planCount.entries()) {
    if (!knownSpacecraft.has(spacecraftId)) {
      platformItems.push({
        spacecraftId,
        noradId: spacecraftId,
        planCount: count,
        hasProblemStatus: problemSpacecraft.has(spacecraftId)
      });
    }
  }

  return platformItems.sort((a, b) => platformLabel(a).localeCompare(platformLabel(b), "ru"));
}

/**
 * Статусы, означающие, что цепочка после отказа уже сдвинулась с места: оператор создал и отправил
 * исправленный план (он в полёте) либо план уже принят/заложен. Проекция PLANNED и черновики DRAFT
 * сюда НЕ входят — это ещё не действие оператора, пауза не снимается.
 */
const CHAIN_ADVANCED_STATUSES = new Set([
  "ISSUING",
  "WAITING_DECISION",
  "AWAITING_LAYIN",
  "LAID_IN",
  "ACCEPTED"
]);

/**
 * КА, у которых цепочка стоит на паузе после REJECTED: есть отклонённый план и НЕТ ни одного
 * «продвинувшего цепочку» плана (см. [CHAIN_ADVANCED_STATUSES]) со стартом не раньше последнего
 * отклонённого окна. Совпадает с серверной паузой ([IssueDuePlanWorker] не выдаёт окно с REJECTED) —
 * оператор должен вручную создать исправленный план.
 */
/**
 * КА с НЕразрешённой проблемой — для красного индикатора. Проблемный план (REJECTED/START_AMBIGUOUS)
 * считается разрешённым и НЕ красит КА, если он либо снят с учёта (`dismissedAt`), либо цепочка уже
 * продвинулась за него (есть [CHAIN_ADVANCED_STATUSES]-план со стартом не раньше). Так исправленные
 * и снятые с учёта отказы гаснут сами, а реальные нерешённые — остаются красными.
 */
export function unresolvedProblemSpacecraftIds(plans: TguPlanUi[]): Set<string> {
  const latestAdvancedStart = new Map<string, number>();
  for (const plan of plans) {
    if (CHAIN_ADVANCED_STATUSES.has(plan.status)) {
      const prev = latestAdvancedStart.get(plan.spacecraftId) ?? Number.NEGATIVE_INFINITY;
      if (plan.startMs > prev) latestAdvancedStart.set(plan.spacecraftId, plan.startMs);
    }
  }

  const unresolved = new Set<string>();
  for (const plan of plans) {
    if (!PROBLEM_STATUSES.has(plan.status)) continue;
    if (plan.dismissedAt) continue; // снят с учёта оператором
    const advancedStart = latestAdvancedStart.get(plan.spacecraftId);
    if (advancedStart !== undefined && advancedStart >= plan.startMs) continue; // цепочка ушла дальше
    unresolved.add(plan.spacecraftId);
  }
  return unresolved;
}

export function pausedSpacecraftIds(plans: TguPlanUi[]): Set<string> {
  const latestRejectedStart = new Map<string, number>();
  for (const plan of plans) {
    if (plan.status === "REJECTED") {
      const prev = latestRejectedStart.get(plan.spacecraftId) ?? Number.NEGATIVE_INFINITY;
      if (plan.startMs > prev) latestRejectedStart.set(plan.spacecraftId, plan.startMs);
    }
  }

  const paused = new Set<string>();
  for (const [spacecraftId, rejectedStart] of latestRejectedStart) {
    const advanced = plans.some(
      (plan) =>
        plan.spacecraftId === spacecraftId &&
        CHAIN_ADVANCED_STATUSES.has(plan.status) &&
        plan.startMs >= rejectedStart
    );
    if (!advanced) paused.add(spacecraftId);
  }
  return paused;
}

export function buildTimelineRows(
  plans: TguPlanUi[],
  platforms: TguPlatformUi[],
  range: TimelineRange,
  selectedSpacecraftId?: string
): TimelineRow[] {
  const platformBySpacecraft = new Map(platforms.map((platform) => [platform.spacecraftId, platform]));
  const rowsBySpacecraft = new Map<string, TimelineRow>();

  for (const plan of filterPlansByRange(plans, range)) {
    if (selectedSpacecraftId && plan.spacecraftId !== selectedSpacecraftId) {
      continue;
    }

    const row =
      rowsBySpacecraft.get(plan.spacecraftId) ??
      {
        spacecraftId: plan.spacecraftId,
        platform: platformBySpacecraft.get(plan.spacecraftId),
        plans: []
      };

    row.plans.push(plan);
    rowsBySpacecraft.set(plan.spacecraftId, row);
  }

  for (const platform of platforms) {
    if (selectedSpacecraftId && platform.spacecraftId !== selectedSpacecraftId) {
      continue;
    }
    if (!rowsBySpacecraft.has(platform.spacecraftId) && platform.planCount > 0) {
      rowsBySpacecraft.set(platform.spacecraftId, {
        spacecraftId: platform.spacecraftId,
        platform,
        plans: []
      });
    }
  }

  return Array.from(rowsBySpacecraft.values())
    .map((row) => ({
      ...row,
      platform: row.platform ?? platformBySpacecraft.get(row.spacecraftId),
      plans: [...row.plans].sort((a, b) => a.startMs - b.startMs)
    }))
    .sort((a, b) => platformLabel(a.platform, a.spacecraftId).localeCompare(platformLabel(b.platform, b.spacecraftId), "ru"));
}

export function filterPlatforms(platforms: TguPlatformUi[], query: string): TguPlatformUi[] {
  const normalized = query.trim().toLowerCase();
  if (!normalized) return platforms;

  return platforms.filter((platform) => {
    const searchText = [
      platform.name,
      platform.noradId,
      platform.spacecraftId,
      platform.mission,
      platform.status
    ]
      .filter(Boolean)
      .join(" ")
      .toLowerCase();

    return searchText.includes(normalized);
  });
}

export function filterPlansByRange(plans: TguPlanUi[], range: TimelineRange): TguPlanUi[] {
  return plans.filter((plan) => plan.endMs >= range.fromMs && plan.startMs <= range.toMs);
}

export function platformLabel(platform?: TguPlatformUi, fallback?: string): string {
  return platform?.name || platform?.noradId || fallback || platform?.spacecraftId || "КА";
}

function buildPlatformByNorad(platforms: TguPlatform[]): Map<string, TguPlatform> {
  const result = new Map<string, TguPlatform>();
  for (const platform of platforms) {
    if (platform.noradId !== null && platform.noradId !== undefined) {
      result.set(String(platform.noradId), platform);
    }
  }
  return result;
}

function toPlatformUi(
  spacecraftId: string,
  platform: TguPlatform,
  planCount: number,
  hasProblemStatus: boolean
): TguPlatformUi {
  return {
    spacecraftId,
    name: platform.name ?? undefined,
    noradId: platform.noradId === null || platform.noradId === undefined ? undefined : String(platform.noradId),
    status: platform.status ?? undefined,
    mission: platform.mission ?? undefined,
    planCount,
    hasProblemStatus,
    platform
  };
}
