import type { Plugin } from "@opencode-ai/plugin";
import type { JsonValue } from "agentlens-sdk";
import { init, flush, EventType as EventTypeValues } from "agentlens-sdk";
import { loadConfig } from "./config.js";
import { SessionState } from "./state.js";
import { truncate, safeJsonValue } from "./utils.js";

/**
 * OpenCode Event shapes (from @opencode-ai/sdk):
 *
 * session.created  → { type, properties: { info: Session } }
 * session.idle     → { type, properties: { sessionID: string } }
 * session.deleted  → { type, properties: { info: Session } }
 * session.error    → { type, properties: { sessionID?: string, error?: ... } }
 * session.diff     → { type, properties: { sessionID: string, diff: FileDiff[] } }
 * file.edited      → { type, properties: { file: string } }
 *
 * Session = { id, projectID, directory, title, ... }
 */

const plugin: Plugin = async ({ project, directory, worktree }) => {
  const config = loadConfig();

  if (!config.enabled || !config.apiKey) {
    return {};
  }



  init({
    apiKey: config.apiKey,
    endpoint: config.endpoint,
    flushInterval: config.flushInterval,
    maxBatchSize: config.maxBatchSize,
  });

  const state = new SessionState();

  /** Helper: get a session ID from the active traces (fallback for events that lack one) */
  function getAnySessionId(): string | undefined {
    return state.getActiveSessionIds()[0];
  }

  return {
    event: async ({ event }) => {
      const type = event.type;
      const props = (event as Record<string, unknown>).properties as
        | Record<string, unknown>
        | undefined;

      if (type === "session.created") {
        // props.info is a Session object with { id, projectID, ... }
        const info = props?.["info"] as
          | Record<string, unknown>
          | undefined;
        const sessionId = info?.["id"] as string | undefined;
        if (sessionId) {
          state.startSession(sessionId, {
            project: project.id,
            directory,
            worktree,
            title: info?.["title"] as string | undefined,
          });

        }
      }

      if (type === "session.idle") {
        // props.sessionID is the session ID string
        const sessionId =
          (props?.["sessionID"] as string) || getAnySessionId();
        if (sessionId) {
          // Flush intermediate trace so data isn't lost if session ends abruptly
          state.flushSession(sessionId);
          await flush();
        }
      }

      if (type === "session.error") {
        const sessionId =
          (props?.["sessionID"] as string) || getAnySessionId() || "";
        if (sessionId) {
          const trace = state.getTrace(sessionId);
          if (trace) {
            const error = props?.["error"] as
              | Record<string, unknown>
              | undefined;
            trace.addEvent({
              type: EventTypeValues.ERROR,
              name: String(
                error?.["name"] ?? error?.["message"] ?? "session error",
              ),
              metadata: safeJsonValue(error ?? props) as JsonValue,
            });
          }
        }
      }

      if (type === "session.deleted") {
        // props.info is a Session object with { id, ... }
        const info = props?.["info"] as
          | Record<string, unknown>
          | undefined;
        const sessionId =
          (info?.["id"] as string) || getAnySessionId() || "";
        if (sessionId) {
          state.endSession(sessionId);
          await flush();

        }
      }

      if (type === "session.diff") {
        // props.sessionID + props.diff (FileDiff[])
        const sessionId =
          (props?.["sessionID"] as string) || getAnySessionId() || "";
        if (sessionId) {
          const trace = state.getTrace(sessionId);
          if (trace) {
            const diffs = props?.["diff"];
            trace.setMetadata(
              safeJsonValue({
                diff: Array.isArray(diffs)
                  ? diffs.map((d: Record<string, unknown>) => ({
                      path: d?.["path"],
                      additions: d?.["additions"],
                      deletions: d?.["deletions"],
                    }))
                  : diffs,
              }) as JsonValue,
            );
          }
        }
      }

      if (type === "file.edited") {
        // props.file is a string (file path), no sessionID on this event
        const file = props?.["file"] as string | undefined;
        const sessionId = getAnySessionId();
        const trace = sessionId ? state.getTrace(sessionId) : undefined;
        if (trace && file) {
          trace.addEvent({
            type: EventTypeValues.CUSTOM,
            name: "file.edited",
            metadata: safeJsonValue({ filePath: file }) as JsonValue,
          });
        }
      }
    },

    "tool.execute.before": async (input, output) => {
      // Auto-create session if we missed session.created event
      if (!state.getTrace(input.sessionID)) {
        state.startSession(input.sessionID, {
          project: project.id,
          directory,
          worktree,
        });

      }
      state.startToolCall(
        input.callID,
        input.tool,
        output.args as unknown,
        input.sessionID,
      );
    },

    "tool.execute.after": async (input, output) => {
      state.endToolCall(
        input.callID,
        truncate(output.output ?? "", config.maxOutputLength),
        output.title ?? input.tool ?? "unknown-tool",
        output.metadata as unknown,
      );
    },

    "chat.message": async (input) => {
      // Auto-create session if we missed session.created event
      if (!state.getTrace(input.sessionID)) {
        state.startSession(input.sessionID, {
          project: project.id,
          directory,
          worktree,
        });

      }
      if (input.model) {
        state.recordLLMCall(input.sessionID, {
          model: input.model,
          agent: input.agent,
          messageID: input.messageID,
        });
      }
    },

    "chat.params": async (input, output) => {
      const trace = state.getTrace(input.sessionID);
      if (trace) {
        trace.addEvent({
          type: EventTypeValues.CUSTOM,
          name: "chat.params",
          metadata: safeJsonValue({
            agent: input.agent,
            model: input.model?.id,
            provider: input.provider?.info?.id,
            temperature: output.temperature,
            topP: output.topP,
            topK: output.topK,
          }) as JsonValue,
        });
      }
    },

    "permission.ask": async (input, output) => {
      state.recordPermission(input.sessionID, input, output.status);
    },
  };
};

export default plugin;
export { plugin as AgentLensPlugin };
export type { PluginConfig } from "./config.js";
export { loadConfig } from "./config.js";
