import type { TguPlanUi } from "../../model/timelineTypes";
import { parseLocal } from "../../utils/localTime";
import type { PlanMode } from "./editorApi";
import type {
  Draft,
  DraftHistory,
  EditorDownlinkWork,
  EditorGroundStation,
  EditorShootWork,
  EditorTarget,
  EditorWork,
  EditorWorkPatch
} from "./model/editorTypes";

const DEFAULT_HISTORY_LIMIT = 50;

export type AddShootInput = {
  id: string;
  startMs: number;
  endMs: number;
  label: string;
  modeId?: string;
  target: EditorTarget;
  /** id сохранённого режима съёмки в БД черновика (если съёмка персистнута). */
  planModeId?: number;
  /** Параметры построенного маршрута (если съёмка строилась с превью). */
  revolution?: number;
  rollDeg?: number;
  captureAngleDeg?: number;
  centerLat?: number;
  centerLon?: number;
  /** Объём маршрута, МБ (из ответа бэка). */
  volumeMb?: number;
};

export type AddDownlinkInput = {
  id: string;
  startMs: number;
  endMs: number;
  label: string;
  station: EditorGroundStation;
  /** id сохранённого режима сброса в БД черновика (если сброс персистнут). */
  planModeId?: number;
  /** Объём сброса, МБ (из ответа бэка). */
  volumeMb?: number;
};

export function seedDraft(plan?: TguPlanUi, spacecraftId?: string): Draft {
  if (!plan) {
    return { spacecraftId, works: [] };
  }

  return {
    planId: plan.planId,
    spacecraftId: plan.spacecraftId,
    // Черновик содержит только правки, внесённые в редакторе. Существующие включения
    // плана грузятся отдельно (fetchPlanModes) и показываются как read-only.
    works: readEmbeddedWorks(plan)
  };
}

/**
 * Преобразует включения плана (plan modes из pcp-tgu-service) в работы для отображения
 * на таймлайне редактора. Это read-only представление фактического состава плана:
 * SURVEY → съёмка, DROP → сброс на НКПОР.
 */
export function planModesToWorks(modes: PlanMode[]): EditorWork[] {
  return modes.flatMap((mode): EditorWork[] => {
    if (mode.timeStart == null) return [];
    const startMs = parseLocal(mode.timeStart);
    if (!Number.isFinite(startMs)) return [];
    const endMs = startMs + Math.max(1, mode.duration) * 1000;

    if (mode.type === "SURVEY") {
      return [{
        id: `survey-${mode.id}`,
        kind: "shoot",
        label: `Съёмка · виток ${mode.revolution}`,
        startMs,
        endMs,
        origin: "plan",
        planModeId: mode.id,
        revolution: mode.revolution,
        rollDeg: mode.roll,
        centerLat: mode.lat,
        centerLon: mode.longitude,
        volumeMb: mode.volumeMb,
        // Центр контура — он же опорная точка для проверки видимости.
        targetLat: mode.lat,
        targetLon: mode.longitude
      }];
    }

    return [{
      id: `drop-${mode.id}`,
      kind: "downlink",
      label: mode.station ? `Сброс · ${mode.station}` : "Сброс на НКПОР",
      startMs,
      endMs,
      origin: "plan",
      planModeId: mode.id,
      revolution: mode.revolution,
      stationId: mode.station ?? undefined,
      surveyIds: mode.surveys,
      volumeMb: mode.volumeMb
    }];
  });
}


export function mutate(
  history: DraftHistory,
  transform: (draft: Draft) => Draft,
  historyLimit = DEFAULT_HISTORY_LIMIT
): DraftHistory {
  const nextDraft = transform(history.draft);
  if (nextDraft === history.draft) {
    return history;
  }

  return {
    draft: nextDraft,
    past: [...history.past, history.draft].slice(-historyLimit),
    future: []
  };
}

export function undo(history: DraftHistory): DraftHistory {
  if (history.past.length === 0) {
    return history;
  }

  return {
    draft: history.past[history.past.length - 1],
    past: history.past.slice(0, -1),
    future: [history.draft, ...history.future]
  };
}

export function redo(history: DraftHistory): DraftHistory {
  if (history.future.length === 0) {
    return history;
  }

  return {
    draft: history.future[0],
    past: [...history.past, history.draft],
    future: history.future.slice(1)
  };
}

export function addShoot(draft: Draft, input: AddShootInput): Draft {
  const work: EditorShootWork = {
    id: input.id,
    kind: "shoot",
    label: input.label,
    startMs: input.startMs,
    endMs: input.endMs,
    modeId: input.modeId,
    targetName: input.target.name,
    targetLat: input.target.lat,
    targetLon: input.target.lon,
    planModeId: input.planModeId,
    revolution: input.revolution,
    rollDeg: input.rollDeg,
    captureAngleDeg: input.captureAngleDeg,
    centerLat: input.centerLat,
    centerLon: input.centerLon,
    volumeMb: input.volumeMb,
    origin: "added",
    isNew: true
  };

  return { ...draft, works: [...draft.works, work] };
}

export function addDownlink(draft: Draft, input: AddDownlinkInput): Draft {
  const work: EditorDownlinkWork = {
    id: input.id,
    kind: "downlink",
    label: input.label,
    startMs: input.startMs,
    endMs: input.endMs,
    stationId: input.station.id,
    planModeId: input.planModeId,
    volumeMb: input.volumeMb,
    origin: "added",
    isNew: true
  };

  return { ...draft, works: [...draft.works, work] };
}

export function updateWork(draft: Draft, workId: string, patch: EditorWorkPatch): Draft {
  return {
    ...draft,
    works: draft.works.map((work) => (work.id === workId ? ({ ...work, ...patch } as EditorWork) : work))
  };
}

export function deleteWork(draft: Draft, workId: string): Draft {
  return {
    ...draft,
    works: draft.works.filter((work) => work.id !== workId)
  };
}

function readEmbeddedWorks(plan: TguPlanUi): EditorWork[] {
  const rawWorks = (plan as TguPlanUi & { works?: unknown }).works;
  if (!Array.isArray(rawWorks)) {
    return [];
  }

  return rawWorks.flatMap((value, index): EditorWork[] => {
    if (!isRecord(value)) return [];
    const kind = value.kind === "shoot" || value.kind === "downlink" ? value.kind : undefined;
    const startMs = readTime(value, "startMs") ?? readTime(value, "start");
    const endMs = readTime(value, "endMs") ?? readTime(value, "end");
    if (!kind || startMs === undefined || endMs === undefined || startMs >= endMs) {
      return [];
    }

    const id = readString(value.id) ?? `${plan.planId}:work-${index + 1}`;
    const label = readString(value.label) ?? (kind === "shoot" ? "Съёмка" : "Сброс на НКПОР");
    const base = {
      id,
      kind,
      label,
      startMs,
      endMs,
      origin: "plan" as const
    };

    if (kind === "shoot") {
      return [{
        ...base,
        kind,
        modeId: readString(value.modeId),
        targetName: readString(value.targetName),
        targetLat: readNumber(value.targetLat),
        targetLon: readNumber(value.targetLon)
      }];
    }

    return [{
      ...base,
      kind,
      stationId: readString(value.stationId) ?? readString(value.kppId)
    }];
  });
}

function readTime(record: Record<string, unknown>, key: string): number | undefined {
  const value = record[key];
  if (typeof value === "number" && Number.isFinite(value)) {
    return value;
  }
  if (typeof value === "string") {
    const parsed = parseLocal(value);
    return Number.isFinite(parsed) ? parsed : undefined;
  }
  return undefined;
}

function readString(value: unknown): string | undefined {
  return typeof value === "string" && value.trim() ? value : undefined;
}

function readNumber(value: unknown): number | undefined {
  return typeof value === "number" && Number.isFinite(value) ? value : undefined;
}

function isRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === "object" && value !== null;
}
