import { LiveChannelScope, isLiveChannelMessageEvent } from '@grafana/data';
import { getBackendSrv, logDebug, getGrafanaLiveSrv } from '@grafana/runtime';
import { useState, useCallback, useEffect } from 'react';
import { useAsync } from 'react-use';
import { pipe } from 'rxjs';
import { scan, filter, toArray, map, tap, takeWhile } from 'rxjs/operators';
import { v4 } from 'uuid';
import { LLM_PLUGIN_ROUTE, setLLMPluginVersion, LLM_PLUGIN_ID } from './constants.mjs';

const LLM_CHAT_COMPLETIONS_PATH = "llm/v1/chat/completions";
var Model = /* @__PURE__ */ ((Model2) => {
  Model2["BASE"] = "base";
  Model2["LARGE"] = "large";
  return Model2;
})(Model || {});
function isContentMessage(message) {
  return "content" in message;
}
function isDoneMessage(message) {
  return "done" in message && message.done != null;
}
function isErrorResponse(response) {
  return "error" in response;
}
function isFunctionCallMessage(message) {
  return "name" in message && "arguments" in message;
}
function isToolCallsMessage(message) {
  return "tool_calls" in message && message.tool_calls != null;
}
function extractContent() {
  return pipe(
    filter(
      (response) => response.choices.length > 0 && isContentMessage(response.choices[0].delta)
    ),
    // The type assertion is needed here because the type predicate above doesn't seem to propagate.
    map(
      (response) => response.choices[0].delta.content
    )
  );
}
function accumulateContent() {
  return pipe(
    extractContent(),
    scan((acc, curr) => acc + curr, "")
  );
}
async function chatCompletions(request) {
  const response = await getBackendSrv().post(
    `/api/plugins/grafana-llm-app/resources/${LLM_CHAT_COMPLETIONS_PATH}`,
    request,
    {
      headers: { "Content-Type": "application/json" }
    }
  );
  return response;
}
function streamChatCompletions(request) {
  const channel = {
    scope: LiveChannelScope.Plugin,
    namespace: LLM_PLUGIN_ID,
    path: LLM_CHAT_COMPLETIONS_PATH + "/" + v4(),
    data: request
  };
  const messages = getGrafanaLiveSrv().getStream(channel).pipe(filter((event) => isLiveChannelMessageEvent(event)));
  return messages.pipe(
    // Filter out messages that don't have the expected structure
    filter((event) => {
      if (!event.message.choices) {
        return false;
      }
      return true;
    }),
    tap((event) => {
      if (isErrorResponse(event.message)) {
        throw new Error(event.message.error);
      }
    }),
    // Stop the stream when we get a done message or when the finish_reason is "stop"
    takeWhile((event) => {
      if (isErrorResponse(event.message)) {
        return true;
      }
      if (event.message.choices && event.message.choices[0].delta && "done" in event.message.choices[0].delta && event.message.choices[0].delta.done === true) {
        return false;
      }
      if (event.message.choices && "finish_reason" in event.message.choices[0] && event.message.choices[0].finish_reason === "stop") {
        return false;
      }
      return true;
    }),
    map((event) => event.message)
  );
}
let loggedWarning$1 = false;
const health$1 = async () => {
  try {
    const settings = await getBackendSrv().get(
      `${LLM_PLUGIN_ROUTE}/settings`,
      void 0,
      void 0,
      {
        showSuccessAlert: false,
        showErrorAlert: false
      }
    );
    if (!settings.enabled) {
      return {
        configured: false,
        ok: false,
        error: "The Grafana LLM plugin is not enabled."
      };
    }
  } catch (e) {
    logDebug(String(e));
    logDebug(
      "Failed to check if LLM provider is enabled. This is expected if the Grafana LLM plugin is not installed, and the above error can be ignored."
    );
    loggedWarning$1 = true;
    return {
      configured: false,
      ok: false,
      error: "The Grafana LLM plugin is not installed."
    };
  }
  let response;
  try {
    response = await getBackendSrv().get(
      `${LLM_PLUGIN_ROUTE}/health`,
      void 0,
      void 0,
      {
        showSuccessAlert: false,
        showErrorAlert: false
      }
    );
  } catch (e) {
    if (!loggedWarning$1) {
      logDebug(String(e));
      logDebug(
        "Failed to check if LLM provider is enabled. This is expected if the Grafana LLM plugin is not installed, and the above error can be ignored."
      );
      loggedWarning$1 = true;
    }
    return {
      configured: false,
      ok: false,
      error: "The Grafana LLM plugin is not installed."
    };
  }
  const { details } = response;
  if (details?.version !== void 0) {
    setLLMPluginVersion(details.version);
  }
  if (details?.llmProvider === void 0) {
    return {
      configured: false,
      ok: false,
      error: "The Grafana LLM plugin is outdated; please update it."
    };
  }
  return typeof details.llmProvider === "boolean" ? { configured: details.llmProvider, ok: details.llmProvider } : details.llmProvider;
};
const enabled$3 = async () => {
  const healthDetails = await health$1();
  return healthDetails.configured && healthDetails.ok;
};
var StreamStatus = /* @__PURE__ */ ((StreamStatus2) => {
  StreamStatus2["IDLE"] = "idle";
  StreamStatus2["GENERATING"] = "generating";
  StreamStatus2["COMPLETED"] = "completed";
  return StreamStatus2;
})(StreamStatus || {});
const TIMEOUT = 1e4;
function useLLMStream(model = "large" /* LARGE */, temperature = 1, notifyError = () => {
}) {
  const [messages, setMessages] = useState([]);
  const [reply, setReply] = useState("");
  const [streamStatus, setStreamStatus] = useState(
    "idle" /* IDLE */
  );
  const [error, setError] = useState();
  const onError = useCallback(
    (e) => {
      setStreamStatus("idle" /* IDLE */);
      setMessages([]);
      setError(e);
      notifyError(
        "Failed to generate content using LLM provider",
        `Please try again or if the problem persists, contact your organization admin.`
      );
      console.error(e);
    },
    [notifyError]
  );
  const { error: enabledError, value: isEnabled } = useAsync(
    async () => await enabled$3(),
    [enabled$3]
  );
  const { error: asyncError, value } = useAsync(async () => {
    if (!isEnabled || !messages.length) {
      return { enabled: isEnabled };
    }
    setStreamStatus("generating" /* GENERATING */);
    setError(void 0);
    const stream = streamChatCompletions({
      model,
      temperature,
      messages
    }).pipe(
      // Accumulate the stream content into a stream of strings, where each
      // element contains the accumulated message so far.
      accumulateContent()
      // The stream is just a regular Observable, so we can use standard rxjs
      // functionality to update state, e.g. recording when the stream
      // has completed.
      // The operator decision tree on the rxjs website is a useful resource:
      // https://rxjs.dev/operator-decision-tree.)
    );
    return {
      enabled: isEnabled,
      stream: stream.subscribe({
        next: setReply,
        error: onError,
        complete: () => {
          setStreamStatus("completed" /* COMPLETED */);
          setTimeout(() => {
            setStreamStatus("idle" /* IDLE */);
          });
          setMessages([]);
          setError(void 0);
        }
      })
    };
  }, [messages, isEnabled]);
  useEffect(() => {
    return () => {
      if (value?.stream) {
        value.stream.unsubscribe();
      }
    };
  }, [value]);
  useEffect(() => {
    let timeout;
    if (streamStatus === "generating" /* GENERATING */ && reply === "") {
      timeout = setTimeout(() => {
        onError(new Error(`LLM stream timed out after ${TIMEOUT}ms`));
      }, TIMEOUT);
    }
    return () => {
      timeout && clearTimeout(timeout);
    };
  }, [streamStatus, reply, onError]);
  if (asyncError || enabledError) {
    setError(asyncError || enabledError);
  }
  return {
    setMessages,
    reply,
    streamStatus,
    error,
    value
  };
}
function accumulateToolCalls() {
  return pipe(
    filter(
      (response) => isToolCallsMessage(response.choices[0].delta)
    ),
    // Collect all tool call chunks
    toArray(),
    // Process the array to reconstruct the complete tool call message
    map((responses) => {
      const toolCallChunks = responses.map(
        (r) => r.choices[0].delta
      );
      return recoverToolCallMessage(toolCallChunks);
    })
  );
}
function recoverToolCallMessage(toolCallMessages) {
  const recoveredToolCallMessage = {
    role: "assistant",
    tool_calls: []
  };
  for (const msg of toolCallMessages) {
    for (const tc of msg.tool_calls) {
      if (tc.index >= recoveredToolCallMessage.tool_calls.length) {
        recoveredToolCallMessage.tool_calls.push({
          ...tc,
          function: { ...tc.function, arguments: tc.function.arguments ?? "" }
        });
      } else {
        recoveredToolCallMessage.tool_calls[tc.index].function.arguments += tc.function.arguments ?? "";
      }
    }
  }
  for (const tc of recoveredToolCallMessage.tool_calls) {
    if (!tc.function.arguments) {
      tc.function.arguments = "{}";
    }
  }
  return recoveredToolCallMessage;
}

export { Model, StreamStatus, TIMEOUT, accumulateContent, accumulateToolCalls, chatCompletions, enabled$3 as enabled, extractContent, health$1 as health, isContentMessage, isDoneMessage, isErrorResponse, isFunctionCallMessage, isToolCallsMessage, recoverToolCallMessage, streamChatCompletions, useLLMStream };
//# sourceMappingURL=llm.mjs.map
