import { listen } from "@tauri-apps/api/event";
import { invoke } from "@tauri-apps/api/core";
import { get } from "svelte/store";
import { claudeStore } from "$lib/stores/claude";
import { characterState } from "$lib/stores/character";
import { configStore } from "$lib/stores/config";
import { initStatsListener, resetSessionStats } from "$lib/stores/stats";
import { initAchievementsListener } from "$lib/stores/achievements";
import type {
  ConnectionStatus,
  PermissionPromptEvent,
  UserQuestionEvent,
} from "$lib/types/messages";
import type { CharacterState } from "$lib/types/states";
import type { AgentStartPayload, AgentEndPayload } from "$lib/types/agents";
import { agentStore } from "$lib/stores/agents";
import { todos } from "$lib/stores/todos";
import {
  initializeNotificationRules,
  cleanupNotificationRules,
  handleConnectionStatusChange,
  handleNewUserMessage,
} from "$lib/notifications/rules";
import { notificationManager } from "$lib/notifications/notificationManager";

interface StateChangePayload {
  state: CharacterState;
  tool_name: string | null;
  conversation_id?: string;
}

const connectedConversations = new Set<string>();
const greetingPendingConversations = new Set<string>();
let unlisteners: Array<() => void> = [];
let skipNextGreeting = false;

export function setSkipNextGreeting(skip: boolean) {
  skipNextGreeting = skip;
}

function getTimeOfDay(): string {
  const hour = new Date().getHours();

  if (hour >= 5 && hour < 12) {
    return "morning";
  } else if (hour >= 12 && hour < 17) {
    return "afternoon";
  } else if (hour >= 17 && hour < 21) {
    return "evening";
  } else {
    return "late night";
  }
}

function generateGreetingPrompt(): string {
  const timeOfDay = getTimeOfDay();
  return `[System: A new session has started. It's currently ${timeOfDay}. Please greet the user warmly and briefly. Keep it short - just 1-2 sentences.]`;
}

async function sendGreeting(conversationId: string): Promise<boolean> {
  // Check if we should skip this greeting
  if (skipNextGreeting) {
    skipNextGreeting = false; // Reset the flag
    return false;
  }

  const config = configStore.getConfig();

  if (!config.greeting_enabled) {
    return false;
  }

  const greetingPrompt = config.greeting_custom_prompt?.trim() || generateGreetingPrompt();

  // Don't show the system prompt in the UI - just trigger Claude to respond
  characterState.setState("thinking");

  // Reset notification state for greeting
  handleNewUserMessage();

  try {
    await invoke("send_prompt", {
      conversationId,
      message: greetingPrompt,
    });
    return true;
  } catch (error) {
    console.error("Failed to send greeting:", error);
    claudeStore.addLineToConversation(conversationId, "error", `Failed to send greeting: ${error}`);
    characterState.setTemporaryState("error", 3000);
    return false;
  }
}

interface OutputPayload {
  line_type: string;
  content: string;
  tool_name: string | null;
  conversation_id?: string;
  cost?: {
    input_tokens: number;
    output_tokens: number;
    cost_usd: number;
  };
  parent_tool_use_id?: string;
}

interface ConnectionPayload {
  status: ConnectionStatus;
  conversation_id?: string;
}

interface SessionPayload {
  session_id: string;
  conversation_id?: string;
}

interface WorkingDirectoryPayload {
  directory: string;
  conversation_id?: string;
}

export async function cleanupConversationTracking(conversationId: string) {
  connectedConversations.delete(conversationId);
  greetingPendingConversations.delete(conversationId);

  // Clean up any temp files associated with this conversation
  try {
    await invoke("cleanup_temp_files", { conversationId });
  } catch (error) {
    console.error("Failed to cleanup temp files for conversation:", error);
  }
}

export async function initializeTauriListeners() {
  // Cleanup any existing listeners first
  cleanupTauriListeners();

  // Initialize notification rules
  initializeNotificationRules();

  // Initialize stats listener
  await initStatsListener();

  // Initialize achievements listener
  await initAchievementsListener();

  const connectionUnlisten = await listen<ConnectionPayload>("claude:connection", async (event) => {
    const { status, conversation_id } = event.payload;

    // Update connection status for the specific conversation
    if (conversation_id) {
      claudeStore.setConnectionStatusForConversation(conversation_id, status);
    } else {
      // Fallback to active conversation if no conversation_id
      claudeStore.setConnectionStatus(status);
    }

    // Handle notification for connection status
    handleConnectionStatusChange(status);

    if (status === "connected") {
      // Get the actual conversation ID (fallback to active if not provided)
      const targetConversationId = conversation_id || get(claudeStore.activeConversationId);

      if (targetConversationId) {
        // Add system message to the correct conversation
        claudeStore.addLineToConversation(
          targetConversationId,
          "system",
          "Connected to Claude Code"
        );

        // Update character state for this conversation
        claudeStore.setCharacterStateForConversation(targetConversationId, "idle");

        // Check if this specific conversation has connected before
        if (!connectedConversations.has(targetConversationId)) {
          connectedConversations.add(targetConversationId);
          resetSessionStats(); // Reset session stats on new connection

          // Immediately hold the tab at yellow while we wait for the greeting response.
          // This avoids a brief green flash before the greeting is even sent.
          greetingPendingConversations.add(targetConversationId);
          claudeStore.setConnectionStatusForConversation(
            targetConversationId,
            "connecting" as ConnectionStatus
          );

          const greetingSent = await sendGreeting(targetConversationId);
          if (!greetingSent) {
            // Greeting was disabled or failed — flip straight to connected.
            greetingPendingConversations.delete(targetConversationId);
            claudeStore.setConnectionStatusForConversation(
              targetConversationId,
              "connected" as ConnectionStatus
            );
          }
        }
      }
    } else if (status === "disconnected") {
      const targetConversationId = conversation_id || get(claudeStore.activeConversationId);

      // Mark all running agents as errored on disconnect, but not during reconnects
      // (permission prompts trigger reconnects and agents may complete before reconnect)
      if (!skipNextGreeting && targetConversationId) {
        agentStore.markAllErrored(targetConversationId);
        // Clear the conversation's agents from the store on real disconnect
        // This prevents agents from persisting across sessions
        agentStore.clearConversation(targetConversationId);
      }

      // Only remove from connected set if we're not about to reconnect
      if (!skipNextGreeting && targetConversationId) {
        connectedConversations.delete(targetConversationId);
        greetingPendingConversations.delete(targetConversationId);
      }

      // Don't add system message if we're about to reconnect
      if (!skipNextGreeting && targetConversationId) {
        claudeStore.addLineToConversation(
          targetConversationId,
          "system",
          "Disconnected from Claude Code unexpectedly — the process may have crashed or been stopped by the system"
        );

        // Clear todos on real disconnect (not on reconnects for permissions)
        todos.clear();
      }

      // Update the tab's connection status on real disconnects
      if (!skipNextGreeting && targetConversationId) {
        claudeStore.setConnectionStatusForConversation(
          targetConversationId,
          "disconnected" as ConnectionStatus
        );
      }

      // Update character state for this conversation
      if (targetConversationId) {
        claudeStore.setCharacterStateForConversation(targetConversationId, "idle");
      }
    } else if (status === "error") {
      const targetConversationId = conversation_id || get(claudeStore.activeConversationId);

      if (targetConversationId) {
        connectedConversations.delete(targetConversationId);
        greetingPendingConversations.delete(targetConversationId);
        claudeStore.addLineToConversation(targetConversationId, "error", "Connection error");
      }

      characterState.setTemporaryState("error", 3000);
    }
  });
  unlisteners.push(connectionUnlisten);

  const stateUnlisten = await listen<StateChangePayload>("claude:state", (event) => {
    const { state, conversation_id } = event.payload;

    const stateMap: Record<string, CharacterState> = {
      idle: "idle",
      thinking: "thinking",
      typing: "typing",
      searching: "searching",
      coding: "coding",
      mcp: "mcp",
      permission: "permission",
      success: "success",
      error: "error",
    };

    const mappedState = stateMap[state.toLowerCase()] || "idle";

    // Per-conversation sound tracking — fires for any tab (active or background).
    // All sounds are driven from state-change events rather than a global store
    // subscription, so background tabs receive their sounds correctly and
    // switching tabs never replays a sound that has already fired.
    const resolvedConversationId = conversation_id || get(claudeStore.activeConversationId) || null;
    if (resolvedConversationId) {
      const conv = get(claudeStore.conversations).get(resolvedConversationId);
      if (conv) {
        const previousState = conv.characterState;

        // New response starting — clear all per-task sound flags.
        // Only reset when entering from a clean-slate state, not mid-task.
        // Transitioning from coding/searching/mcp/typing → thinking means we're
        // still within the same task (between tool calls), so the sound must not replay.
        const cleanSlateStates: CharacterState[] = ["idle", "success", "error"];
        if (mappedState === "thinking" && cleanSlateStates.includes(previousState)) {
          claudeStore.resetSoundState(resolvedConversationId);
        }

        // Record when a long-running phase begins (used for the 2-second
        // minimum duration check before playing the completion sound).
        if (
          (mappedState === "coding" || mappedState === "searching") &&
          previousState !== "coding" &&
          previousState !== "searching"
        ) {
          claudeStore.setTaskStartTime(resolvedConversationId, Date.now());
        }

        // Task-start sound — fires once when work enters a long-running phase.
        if (
          (mappedState === "coding" || mappedState === "searching") &&
          previousState !== "coding" &&
          previousState !== "searching" &&
          !conv.taskStartSoundFired
        ) {
          notificationManager.notifyTaskStart();
          claudeStore.markTaskStartSoundFired(resolvedConversationId);
        }

        // Error sound — fires each time a new error state is entered.
        if (mappedState === "error" && previousState !== "error") {
          notificationManager.notifyError();
        }

        // Permission sound — fires each time a permission request arrives.
        if (mappedState === "permission") {
          notificationManager.notifyPermission();
        }

        // Completion sound — fires once per task after sufficient duration.
        if (mappedState === "success" && !conv.successSoundFired) {
          const duration = conv.taskStartTime ? Date.now() - conv.taskStartTime : 0;
          if (duration > 2000) {
            notificationManager.notifySuccess();
          }
          claudeStore.markSuccessSoundFired(resolvedConversationId);
        }
      }
    }

    // Always update the conversation's state
    if (conversation_id) {
      claudeStore.setCharacterStateForConversation(conversation_id, mappedState);
    } else {
      // Fallback to active conversation if no conversation_id
      const activeConversationId = get(claudeStore.activeConversationId);
      if (activeConversationId) {
        claudeStore.setCharacterStateForConversation(activeConversationId, mappedState);
      }
    }

    // Only update global character state for active conversation
    const activeConversationId = get(claudeStore.activeConversationId);
    if (!conversation_id || conversation_id === activeConversationId) {
      if (mappedState === "success" || mappedState === "error") {
        characterState.setTemporaryState(mappedState, 3000);
      } else {
        characterState.setState(mappedState);
      }
    }
  });
  unlisteners.push(stateUnlisten);

  const outputUnlisten = await listen<OutputPayload>("claude:output", (event) => {
    const { line_type, content, tool_name, conversation_id, cost, parent_tool_use_id } =
      event.payload;

    // Convert snake_case cost to camelCase for TypeScript
    const costData = cost
      ? {
          inputTokens: cost.input_tokens,
          outputTokens: cost.output_tokens,
          costUsd: cost.cost_usd,
        }
      : undefined;

    // Flip to connected when first assistant message arrives after greeting
    if (
      conversation_id &&
      line_type === "assistant" &&
      greetingPendingConversations.has(conversation_id)
    ) {
      greetingPendingConversations.delete(conversation_id);
      claudeStore.setConnectionStatusForConversation(
        conversation_id,
        "connected" as ConnectionStatus
      );
    }

    // Always store the output to the correct conversation
    if (conversation_id) {
      claudeStore.addLineToConversation(
        conversation_id,
        line_type as
          | "user"
          | "assistant"
          | "system"
          | "tool"
          | "error"
          | "thinking"
          | "rate-limit"
          | "compact-prompt"
          | "worktree"
          | "config-change",
        content,
        tool_name || undefined,
        costData,
        parent_tool_use_id
      );
    } else {
      // Fallback to active conversation if no conversation_id provided
      claudeStore.addLine(
        line_type as
          | "user"
          | "assistant"
          | "system"
          | "tool"
          | "error"
          | "thinking"
          | "rate-limit"
          | "compact-prompt"
          | "worktree"
          | "config-change",
        content,
        tool_name || undefined,
        costData,
        parent_tool_use_id
      );
    }
  });
  unlisteners.push(outputUnlisten);

  const streamUnlisten = await listen<string>("claude:stream", () => {
    // no-op
  });
  unlisteners.push(streamUnlisten);

  const sessionUnlisten = await listen<SessionPayload>("claude:session", (event) => {
    const { session_id, conversation_id } = event.payload;

    // Store session ID for the correct conversation
    if (conversation_id) {
      claudeStore.setSessionIdForConversation(conversation_id, session_id);
      claudeStore.addLineToConversation(
        conversation_id,
        "system",
        `Session: ${session_id.substring(0, 8)}...`
      );
    } else {
      // Fallback to active conversation if no conversation_id
      claudeStore.setSessionId(session_id);
      claudeStore.addLine("system", `Session: ${session_id.substring(0, 8)}...`);
    }
  });
  unlisteners.push(sessionUnlisten);

  const cwdUnlisten = await listen<WorkingDirectoryPayload>("claude:cwd", (event) => {
    const { directory, conversation_id } = event.payload;

    // Store working directory for the correct conversation
    if (conversation_id) {
      claudeStore.setWorkingDirectoryForConversation(conversation_id, directory);
    } else {
      // Fallback to active conversation if no conversation_id
      claudeStore.setWorkingDirectory(directory);
    }
  });
  unlisteners.push(cwdUnlisten);

  console.log("[Tauri Listener] Setting up claude:permission listener");
  const permissionUnlisten = await listen<PermissionPromptEvent>("claude:permission", (event) => {
    const { permissions, conversation_id } = event.payload;

    console.log(
      `[Permission] Event received: ${permissions.length} permission(s) for conversation ${conversation_id || "active"}`,
      { permissions, conversation_id }
    );

    // Store each permission request for the specific conversation
    for (const permission of permissions) {
      const { id, tool_name, tool_input, description } = permission;

      if (conversation_id) {
        claudeStore.requestPermissionForConversation(conversation_id, {
          id,
          tool: tool_name,
          description,
          input: tool_input,
        });
        claudeStore.addLineToConversation(
          conversation_id,
          "system",
          `Permission requested for: ${tool_name}`
        );
      } else {
        // Fallback to active conversation if no conversation_id
        claudeStore.requestPermission({
          id,
          tool: tool_name,
          description,
          input: tool_input,
        });
        claudeStore.addLine("system", `Permission requested for: ${tool_name}`);
      }
    }
  });
  unlisteners.push(permissionUnlisten);

  const agentStartUnlisten = await listen<AgentStartPayload>("claude:agent-start", (event) => {
    const {
      tool_use_id,
      agent_id,
      description,
      subagent_type,
      started_at,
      conversation_id,
      parent_tool_use_id,
    } = event.payload;
    const targetConversationId = conversation_id || get(claudeStore.activeConversationId);
    if (targetConversationId) {
      agentStore.addAgent(targetConversationId, {
        toolUseId: tool_use_id,
        agentId: agent_id,
        description,
        subagentType: subagent_type,
        startedAt: started_at,
        status: "running",
        parentToolUseId: parent_tool_use_id,
      });
    }
  });
  unlisteners.push(agentStartUnlisten);

  const agentUpdateUnlisten = await listen<{
    conversationId: string;
    toolUseId: string;
    agentId: string;
  }>("claude:agent-update", (event) => {
    const { conversationId, toolUseId, agentId } = event.payload;
    agentStore.updateAgentId(conversationId, toolUseId, agentId);
  });
  unlisteners.push(agentUpdateUnlisten);

  const agentEndUnlisten = await listen<AgentEndPayload>("claude:agent-end", (event) => {
    const { tool_use_id, ended_at, is_error, conversation_id, last_assistant_message } =
      event.payload;
    const targetConversationId = conversation_id || get(claudeStore.activeConversationId);
    if (targetConversationId) {
      agentStore.endAgent(
        targetConversationId,
        tool_use_id,
        ended_at,
        is_error,
        last_assistant_message
      );
    }
  });
  unlisteners.push(agentEndUnlisten);

  const questionUnlisten = await listen<UserQuestionEvent>("claude:question", (event) => {
    const questionEvent = event.payload;

    // Store question request for the specific conversation
    if (questionEvent.conversation_id) {
      claudeStore.requestQuestionForConversation(questionEvent.conversation_id, questionEvent);
      claudeStore.addLineToConversation(
        questionEvent.conversation_id,
        "system",
        `Question: ${questionEvent.question}`
      );
    } else {
      // Fallback to active conversation if no conversation_id
      claudeStore.requestQuestion(questionEvent);
      claudeStore.addLine("system", `Question: ${questionEvent.question}`);
    }
  });
  unlisteners.push(questionUnlisten);
}

export function cleanupTauriListeners() {
  // Cleanup all event listeners
  unlisteners.forEach((unlisten) => unlisten());
  unlisteners = [];

  // Cleanup notification rules
  cleanupNotificationRules();
}

export async function initializeDiscordRpc() {
  const config = configStore.getConfig();
  if (config.discord_rpc_enabled) {
    try {
      const startedAt = new Date();
      const startedAtUnixSeconds = Math.floor(startedAt.getTime() / 1000);
      const model = config.model || "claude";

      console.log("Initializing Discord RPC with initial activity:", {
        session_name: "Idle",
        model,
        started_at: startedAtUnixSeconds,
      });

      await invoke("init_discord_rpc", {
        sessionName: "Idle",
        model,
        startedAt: startedAtUnixSeconds,
      });

      console.log("Discord RPC initialized successfully with initial presence");
    } catch (error) {
      console.error("Failed to initialize Discord RPC:", error);
      console.warn("Discord RPC will be unavailable. Make sure Discord is running.");
    }
  } else {
    console.log("Discord RPC is disabled in config, skipping initialization");
  }
}

export async function updateDiscordRpc(sessionName: string, model: string, startedAt: Date) {
  const config = configStore.getConfig();
  if (!config.discord_rpc_enabled) {
    return;
  }

  try {
    const startedAtUnixSeconds = Math.floor(startedAt.getTime() / 1000);
    await invoke("update_discord_rpc", {
      sessionName: sessionName,
      model,
      startedAt: startedAtUnixSeconds,
    });
  } catch (error) {
    console.error("Failed to update Discord RPC:", error);
  }
}

export async function stopDiscordRpc() {
  try {
    await invoke("stop_discord_rpc");
  } catch (error) {
    console.error("Failed to stop Discord RPC:", error);
  }
}
