import {
  Component,
  Ref,
  computed,
  effectScope,
  markRaw,
  reactive,
  ref,
  watch,
} from "vue"
import { activeActions$, invokeAction } from "~/helpers/actions"
import { getI18n } from "~/modules/i18n"
import {
  SpotlightSearcher,
  SpotlightSearcherResult,
  SpotlightSearcherSessionState,
  SpotlightService,
} from ".."
import {
  SearchResult,
  StaticSpotlightSearcherService,
} from "./base/static.searcher"

import IconCopy from "~icons/lucide/copy"
import IconEdit from "~icons/lucide/edit"
import IconLayers from "~icons/lucide/layers"
import IconTrash2 from "~icons/lucide/trash-2"

import { Container, Service } from "dioc"
import * as TE from "fp-ts/TaskEither"
import { pipe } from "fp-ts/function"
import { cloneDeep } from "lodash-es"
import MiniSearch from "minisearch"
import { map } from "rxjs"
import { useStreamStatic } from "~/composables/stream"
import { GQLError, runGQLQuery } from "~/helpers/backend/GQLClient"
import { deleteTeamEnvironment } from "~/helpers/backend/mutations/TeamEnvironment"
import {
  SelectedEnvironmentIndex,
  createEnvironment,
  currentEnvironment$,
  duplicateEnvironment,
  environmentsStore,
  getGlobalVariables,
  selectedEnvironmentIndex$,
  setSelectedEnvironmentIndex,
} from "~/newstore/environments"

import * as E from "fp-ts/Either"
import IconCheckCircle from "~/components/app/spotlight/entry/IconSelected.vue"
import { useToast } from "~/composables/toast"
import { GetTeamEnvironmentsDocument } from "~/helpers/backend/graphql"
import { TeamEnvironment } from "~/helpers/teams/TeamEnvironment"
import { WorkspaceService } from "~/services/workspace.service"
import IconCircle from "~icons/lucide/circle"

type Doc = {
  text: string | string[]
  alternates: string[]
  icon: object | Component
  excludeFromSearch?: boolean
}

type SelectedEnv = {
  selected?: boolean
} & (
  | Omit<SelectedEnvironmentIndex & { type: "TEAM_ENV" }, "environment">
  | (SelectedEnvironmentIndex & { type: "MY_ENV" })
)

/**
 *
 * This searcher is responsible for providing environments related actions on the spotlight results.
 *
 * NOTE: Initializing this service registers it as a searcher with the Spotlight Service.
 */
export class EnvironmentsSpotlightSearcherService extends StaticSpotlightSearcherService<Doc> {
  public static readonly ID = "ENVIRONMENTS_SPOTLIGHT_SEARCHER_SERVICE"

  private t = getI18n()

  public readonly searcherID = "environments"
  public searcherSectionTitle = this.t("spotlight.environments.title")

  private readonly spotlight = this.bind(SpotlightService)

  private selectedEnvIndex = useStreamStatic(
    selectedEnvironmentIndex$,
    {
      type: "NO_ENV_SELECTED",
    },
    () => {
      /* noop */
    }
  )[0]

  private selectedEnv = useStreamStatic(currentEnvironment$, null, () => {
    /* noop */
  })[0]

  private hasSelectedEnv = computed(
    () => this.selectedEnvIndex.value?.type !== "NO_ENV_SELECTED"
  )

  private documents: Record<string, Doc> = reactive({
    new_environment: {
      text: [
        this.t("spotlight.environments.title"),
        this.t("spotlight.environments.new"),
      ],
      alternates: ["new", "environment"],
      icon: markRaw(IconLayers),
    },
    new_environment_variable: {
      text: [
        this.t("spotlight.environments.title"),
        this.t("spotlight.environments.new_variable"),
      ],
      alternates: ["new", "environment", "variable"],
      icon: markRaw(IconLayers),
    },
    edit_selected_env: {
      text: [
        this.t("spotlight.environments.title"),
        this.t("spotlight.environments.edit"),
      ],
      alternates: ["edit", "environment"],
      icon: markRaw(IconEdit),
      excludeFromSearch: computed(() => !this.hasSelectedEnv.value),
    },
    delete_selected_env: {
      text: [
        this.t("spotlight.environments.title"),
        this.t("spotlight.environments.delete"),
      ],
      alternates: ["delete", "environment"],
      icon: markRaw(IconTrash2),
      excludeFromSearch: computed(() => !this.hasSelectedEnv.value),
    },
    duplicate_selected_env: {
      text: [
        this.t("spotlight.environments.title"),
        this.t("spotlight.environments.duplicate"),
      ],
      alternates: ["duplicate", "environment"],
      icon: markRaw(IconCopy),
      excludeFromSearch: computed(() => !this.hasSelectedEnv.value),
    },
    edit_global_env: {
      text: [
        this.t("spotlight.environments.title"),
        this.t("spotlight.environments.edit_global"),
      ],
      alternates: ["edit", "global", "environment"],
      icon: markRaw(IconEdit),
    },
    duplicate_global_env: {
      text: [
        this.t("spotlight.environments.title"),
        this.t("spotlight.environments.duplicate_global"),
      ],
      alternates: ["duplicate", "global", "environment"],
      icon: markRaw(IconCopy),
    },
  })

  // TODO: This pattern is no longer recommended in dioc > 3, move to something else
  constructor(c: Container) {
    super(c, {
      searchFields: ["text", "alternates"],
      fieldWeights: {
        text: 2,
        alternates: 1,
      },
    })
  }

  override onServiceInit() {
    this.setDocuments(this.documents)
    this.spotlight.registerSearcher(this)
  }

  protected getSearcherResultForSearchResult(
    result: SearchResult<Doc>
  ): SpotlightSearcherResult {
    return {
      id: result.id,
      icon: result.doc.icon,
      text: { type: "text", text: result.doc.text },
      score: result.score,
    }
  }

  private getSelectedText() {
    const selection = window.getSelection()
    return selection?.toString() ?? ""
  }

  duplicateGlobalEnv() {
    createEnvironment(
      `Global - ${this.t("action.duplicate")}`,
      cloneDeep(getGlobalVariables())
    )
    // this.toast.success(`${t("environment.duplicated")}`)
  }

  duplicateSelectedEnv() {
    if (this.selectedEnvIndex.value?.type === "NO_ENV_SELECTED") return

    if (this.selectedEnvIndex.value?.type === "MY_ENV") {
      duplicateEnvironment(this.selectedEnvIndex.value.index)
      // this.toast.success(`${t("environment.duplicated")}`)
    }

    if (this.selectedEnvIndex.value?.type === "TEAM_ENV") {
      pipe(
        deleteTeamEnvironment(this.selectedEnvIndex.value.teamEnvID),
        TE.match(
          (err: GQLError<string>) => {
            console.error(err)
          },
          () => {
            // this.toast.success(`${this.t("environment.duplicated")}`)
          }
        )
      )()
    }
  }

  public onDocSelected(id: string): void {
    switch (id) {
      case "new_environment":
        invokeAction(`modals.environment.new`)
        break
      case "new_environment_variable":
        invokeAction(`modals.environment.add`, {
          envName: "",
          variableName: this.getSelectedText(),
        })
        break
      case "edit_selected_env":
        if (this.selectedEnv.value)
          invokeAction(`modals.my.environment.edit`, {
            envName: this.selectedEnv.value.name,
          })
        break
      case "delete_selected_env":
        invokeAction(`modals.environment.delete-selected`)
        break
      case "duplicate_selected_env":
        this.duplicateSelectedEnv()
        break
      case "edit_global_env":
        invokeAction(`modals.global.environment.update`, {})
        break
      case "duplicate_global_env":
        this.duplicateGlobalEnv()
        break
    }
  }
}

/**
 * This searcher is responsible for searching through the environment.
 * And switching between them.
 */
export class SwitchEnvSpotlightSearcherService
  extends Service
  implements SpotlightSearcher
{
  public static readonly ID = "SWITCH_ENV_SPOTLIGHT_SEARCHER_SERVICE"

  private t = getI18n()
  private toast = useToast()

  public searcherID = "switch_env"
  public searcherSectionTitle = this.t("tab.environments")

  private readonly spotlight = this.bind(SpotlightService)
  private readonly workspaceService = this.bind(WorkspaceService)
  private teamEnvironmentList: TeamEnvironment[] = []

  override onServiceInit() {
    this.spotlight.registerSearcher(this)
  }

  private selectedEnvIndex = useStreamStatic(
    selectedEnvironmentIndex$,
    {
      type: "NO_ENV_SELECTED",
    },
    () => {
      /* noop */
    }
  )[0]

  private environmentSearchable = useStreamStatic(
    activeActions$.pipe(
      map((actions) => actions.includes("modals.environment.add"))
    ),
    activeActions$.value.includes("modals.environment.add"),
    () => {
      /* noop */
    }
  )[0]

  async fetchTeamEnvironmentList(teamID: string): Promise<TeamEnvironment[]> {
    const results: TeamEnvironment[] = []

    const result = await runGQLQuery({
      query: GetTeamEnvironmentsDocument,
      variables: {
        teamID: teamID,
      },
    })

    if (E.isRight(result)) {
      if (result.right.team) {
        results.push(
          ...result.right.team.teamEnvironments.map(
            ({ id, teamID, name, variables }) =>
              <TeamEnvironment>{
                id: id,
                teamID: teamID,
                environment: {
                  name: name,
                  variables: JSON.parse(variables),
                },
              }
          )
        )
      }
    }

    return results
  }

  createSearchSession(
    query: Readonly<Ref<string>>
  ): [Ref<SpotlightSearcherSessionState>, () => void] {
    const loading = ref(false)
    const results = ref<SpotlightSearcherResult[]>([])

    const minisearch = new MiniSearch({
      fields: ["name", "alternates"],
      storeFields: ["name"],
    })

    if (this.environmentSearchable.value) {
      minisearch.addAll(
        environmentsStore.value.environments.map((entry, index) => {
          const id: SelectedEnv = {
            type: "MY_ENV",
            index,
          }

          if (
            this.selectedEnvIndex.value?.type === "MY_ENV" &&
            this.selectedEnvIndex.value.index === index
          ) {
            id.selected = true
          }
          return {
            id: JSON.stringify(id),
            name: entry.name,
            alternates: ["environment", "change", entry.name],
          }
        })
      )

      const workspace = this.workspaceService.currentWorkspace

      if (workspace.value?.type === "team") {
        this.fetchTeamEnvironmentList(workspace.value.teamID).then(
          (results) => {
            this.teamEnvironmentList = results
            minisearch.addAll(
              results.map(({ teamID, id: teamEnvID, environment }) => {
                const id: SelectedEnv = {
                  type: "TEAM_ENV",
                  teamID,
                  teamEnvID,
                }

                if (
                  this.selectedEnvIndex.value?.type === "TEAM_ENV" &&
                  this.selectedEnvIndex.value.teamEnvID === teamEnvID
                ) {
                  id.selected = true
                }
                return {
                  id: JSON.stringify(id),
                  name: environment.name,
                  alternates: ["environment", "change", environment.name],
                }
              })
            )
          }
        )
      }
    }

    const scopeHandle = effectScope()

    scopeHandle.run(() => {
      watch(
        [query],
        ([query]) => {
          results.value = minisearch
            .search(query, {
              prefix: true,
              fuzzy: true,
              boost: {
                reltime: 2,
              },
              weights: {
                fuzzy: 0.2,
                prefix: 0.8,
              },
            })
            .map(({ id, score, name }) => {
              return {
                id: id,
                icon: markRaw(
                  JSON.parse(id).selected ? IconCheckCircle : IconCircle
                ),
                score: score,
                text: {
                  type: "text",
                  text: [this.t("environment.set"), name],
                },
              }
            })
        },
        { immediate: true }
      )
    })

    const onSessionEnd = () => {
      scopeHandle.stop()
      minisearch.removeAll()
    }

    const resultObj = computed<SpotlightSearcherSessionState>(() => ({
      loading: loading.value,
      results: results.value,
    }))

    return [resultObj, onSessionEnd]
  }

  onResultSelect(result: SpotlightSearcherResult): void {
    try {
      const selectedEnv = JSON.parse(result.id) as SelectedEnv

      if (selectedEnv.type === "MY_ENV") {
        setSelectedEnvironmentIndex({
          type: "MY_ENV",
          index: selectedEnv.index,
        })
      }

      if (selectedEnv.type === "TEAM_ENV") {
        const teamEnv = this.teamEnvironmentList.find(
          ({ id }) => id === selectedEnv.teamEnvID
        )
        if (!teamEnv) return

        const { teamID, teamEnvID } = selectedEnv
        setSelectedEnvironmentIndex({
          type: "TEAM_ENV",
          teamEnvID,
          teamID,
          environment: teamEnv.environment,
        })
      }
    } catch (e) {
      console.error((e as Error).message)
      this.toast.error(this.t("error.something_went_wrong"))
    }
  }
}
