import { defineStore } from "pinia";

import { normalizeApiError, type ApiError } from "../../shared/api/errors";
import {
  cancelRun,
  deleteRunNamespace,
  getRun,
  getRunLogs,
  listPendingApprovals,
  listRunEvents,
  listRunWaits,
  resolveApprovalDecision,
  type RunWaitFilters,
} from "./api";
import type {
  ApprovalRequest,
  FlowEvent,
  ResolveApprovalDecisionResponse,
  Run,
  RunActionResponse,
  RunRealtimeMessage,
  RunLogs,
  RunNamespaceCleanupResponse,
} from "./types";
import { isRunWaitRealtimeMessage } from "./wait-presenters";

const errorAutoHideMs = 5000;
function sortEventsNewest(items: FlowEvent[]): FlowEvent[] {
  return [...items].sort((a, b) => (a.created_at < b.created_at ? 1 : a.created_at > b.created_at ? -1 : 0));
}

export const useRunsStore = defineStore("runs", {
  state: () => ({
    waitQueue: [] as Run[],
    pendingApprovals: [] as ApprovalRequest[],
    waitsFilters: {
      triggerKind: "",
      status: "",
      agentKey: "",
      waitState: "",
    } as RunWaitFilters,
    waitsLoading: false,
    approvalsLoading: false,
    resolvingApprovalID: null as number | null,
    error: null as ApiError | null,
    approvalsError: null as ApiError | null,
    errorTimerId: null as number | null,
    approvalsErrorTimerId: null as number | null,
  }),
  actions: {
    clearErrorTimer(): void {
      if (this.errorTimerId !== null) {
        window.clearTimeout(this.errorTimerId);
        this.errorTimerId = null;
      }
    },
    scheduleErrorHide(): void {
      this.clearErrorTimer();
      this.errorTimerId = window.setTimeout(() => {
        this.error = null;
        this.errorTimerId = null;
      }, errorAutoHideMs);
    },
    clearApprovalsErrorTimer(): void {
      if (this.approvalsErrorTimerId !== null) {
        window.clearTimeout(this.approvalsErrorTimerId);
        this.approvalsErrorTimerId = null;
      }
    },
    scheduleApprovalsErrorHide(): void {
      this.clearApprovalsErrorTimer();
      this.approvalsErrorTimerId = window.setTimeout(() => {
        this.approvalsError = null;
        this.approvalsErrorTimerId = null;
      }, errorAutoHideMs);
    },
    async loadRunWaits(limit?: number): Promise<void> {
      this.waitsLoading = true;
      this.error = null;
      try {
        this.waitQueue = await listRunWaits(this.waitsFilters, limit);
      } catch (e) {
        this.error = normalizeApiError(e);
        this.scheduleErrorHide();
      } finally {
        this.waitsLoading = false;
      }
    },
    async loadPendingApprovals(limit?: number): Promise<void> {
      this.approvalsLoading = true;
      this.approvalsError = null;
      try {
        this.pendingApprovals = await listPendingApprovals(limit);
      } catch (e) {
        this.approvalsError = normalizeApiError(e);
        this.scheduleApprovalsErrorHide();
      } finally {
        this.approvalsLoading = false;
      }
    },
    async resolvePendingApproval(
      approvalRequestId: number,
      decision: "approved" | "denied" | "expired" | "failed",
      reason = "",
      limit?: number,
    ): Promise<ResolveApprovalDecisionResponse | null> {
      this.resolvingApprovalID = approvalRequestId;
      this.approvalsError = null;
      try {
        const response = await resolveApprovalDecision(approvalRequestId, decision, reason);
        await this.loadPendingApprovals(limit);
        return response;
      } catch (e) {
        this.approvalsError = normalizeApiError(e);
        this.scheduleApprovalsErrorHide();
        return null;
      } finally {
        this.resolvingApprovalID = null;
      }
    },
  },
});

export const useRunDetailsStore = defineStore("runDetails", {
  state: () => ({
    runId: "" as string,
    run: null as Run | null,
    loading: false,
    error: null as ApiError | null,
    events: [] as FlowEvent[],
    eventsPayloadLoaded: false,
    eventsPayloadLoading: false,
    logs: null as RunLogs | null,
    snapshotLoaded: false,
    snapshotLoading: false,
    waitRealtimeMessages: [] as RunRealtimeMessage[],
    cancelingRun: false,
    cancelRunError: null as ApiError | null,
    runCancelResult: null as RunActionResponse | null,
    deletingNamespace: false,
    deleteNamespaceError: null as ApiError | null,
    namespaceDeleteResult: null as RunNamespaceCleanupResponse | null,
    errorTimerId: null as number | null,
    cancelRunErrorTimerId: null as number | null,
    deleteNamespaceErrorTimerId: null as number | null,
  }),
  actions: {
    clearErrorTimer(timerField: "errorTimerId" | "cancelRunErrorTimerId" | "deleteNamespaceErrorTimerId"): void {
      const timerId = this[timerField];
      if (timerId !== null) {
        window.clearTimeout(timerId);
        this[timerField] = null;
      }
    },
    scheduleErrorHide(
      errorField: "error" | "cancelRunError" | "deleteNamespaceError",
      timerField: "errorTimerId" | "cancelRunErrorTimerId" | "deleteNamespaceErrorTimerId",
    ): void {
      this.clearErrorTimer(timerField);
      this[timerField] = window.setTimeout(() => {
        this[errorField] = null;
        this[timerField] = null;
      }, errorAutoHideMs);
    },
    async load(runId: string): Promise<void> {
      this.runId = runId;
      this.loading = true;
      this.error = null;
      try {
        const [run, events, logs] = await Promise.all([
          getRun(runId),
          listRunEvents(runId, 200, true),
          getRunLogs(runId, 200, false),
        ]);
        this.run = run;
        this.events = sortEventsNewest(events);
        this.eventsPayloadLoaded = true;
        this.logs = logs;
        this.snapshotLoaded = false;
        this.waitRealtimeMessages = [];
      } catch (e) {
        this.run = null;
        this.events = [];
        this.eventsPayloadLoaded = false;
        this.logs = null;
        this.snapshotLoaded = false;
        this.waitRealtimeMessages = [];
        this.error = normalizeApiError(e);
        this.scheduleErrorHide("error", "errorTimerId");
      } finally {
        this.loading = false;
      }
    },

    async loadEventPayloads(runId: string): Promise<void> {
      if (this.eventsPayloadLoading) {
        return;
      }
      this.eventsPayloadLoading = true;
      try {
        const events = await listRunEvents(runId, 200, true);
        this.events = sortEventsNewest(events);
        this.eventsPayloadLoaded = true;
      } catch (e) {
        this.error = normalizeApiError(e);
        this.scheduleErrorHide("error", "errorTimerId");
      } finally {
        this.eventsPayloadLoading = false;
      }
    },

    async refreshLogs(runId: string, tailLines = 200, includeSnapshot = false): Promise<void> {
      try {
        const freshLogs = await getRunLogs(runId, tailLines, includeSnapshot);
        if (!includeSnapshot && this.snapshotLoaded && this.logs?.snapshot_json) {
          freshLogs.snapshot_json = this.logs.snapshot_json;
        }
        this.logs = freshLogs;
        if (includeSnapshot) {
          this.snapshotLoaded = true;
        }
      } catch (e) {
        this.error = normalizeApiError(e);
        this.scheduleErrorHide("error", "errorTimerId");
      }
    },

    async loadSnapshot(runId: string, tailLines = 200): Promise<void> {
      if (this.snapshotLoading) {
        return;
      }
      this.snapshotLoading = true;
      try {
        await this.refreshLogs(runId, tailLines, true);
      } finally {
        this.snapshotLoading = false;
      }
    },

    applyRealtimeMessage(message: RunRealtimeMessage): void {
      if (message.run && this.runId && message.run.id === this.runId) {
        this.run = message.run;
      }
      if (Array.isArray(message.events)) {
        this.events = sortEventsNewest(message.events);
        this.eventsPayloadLoaded = true;
      }
      if (message.logs) {
        this.logs = message.logs;
      }
      if (isRunWaitRealtimeMessage(message)) {
        this.waitRealtimeMessages = [message, ...this.waitRealtimeMessages].slice(0, 12);
      }
    },

    async cancel(runId: string, reason = ""): Promise<void> {
      this.cancelingRun = true;
      this.cancelRunError = null;
      this.runCancelResult = null;
      try {
        this.runCancelResult = await cancelRun(runId, reason);
        await this.load(runId);
      } catch (e) {
        this.cancelRunError = normalizeApiError(e);
        this.scheduleErrorHide("cancelRunError", "cancelRunErrorTimerId");
      } finally {
        this.cancelingRun = false;
      }
    },

    async deleteNamespace(runId: string): Promise<void> {
      this.deletingNamespace = true;
      this.deleteNamespaceError = null;
      this.namespaceDeleteResult = null;
      try {
        this.namespaceDeleteResult = await deleteRunNamespace(runId);
        await this.load(runId);
      } catch (e) {
        this.deleteNamespaceError = normalizeApiError(e);
        this.scheduleErrorHide("deleteNamespaceError", "deleteNamespaceErrorTimerId");
      } finally {
        this.deletingNamespace = false;
      }
    },
  },
});
