<script setup lang="ts">
import { computed, ref, reactive, watch, onMounted, type PropType } from "vue";
import ParameterGroupForm from "@/components/parameter-schema/form-mode/ParameterGroupForm.vue";
import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue";
import Ajv from "ajv";
import type { ValidateFunction } from "ajv";
import ParameterStringInput from "@/components/parameter-schema/form-mode/ParameterStringInput.vue";
import { Toast, Tooltip } from "bootstrap";
import { useBucketStore } from "@/stores/buckets";
import { useS3KeyStore } from "@/stores/s3keys";
import BootstrapToast from "@/components/BootstrapToast.vue";
import { useResourceStore } from "@/stores/resources";
import { useRoute, useRouter } from "vue-router";
import type { ClowmInfo } from "@/types/ClowmInfo";
import UploadParameterFileModal from "@/components/parameter-schema/UploadParameterFileModal.vue";
import type {
  TemporaryParams,
  WorkflowMetaParameters,
  WorkflowParameters,
} from "@/types/WorkflowParameters";
import { useWorkflowExecutionStore } from "@/stores/workflowExecutions";

const bucketRepository = useBucketStore();
const resourceRepository = useResourceStore();
const keyRepository = useS3KeyStore();
const executionRepository = useWorkflowExecutionStore();
const router = useRouter();
const route = useRoute();

// Props
// =============================================================================
const props = defineProps({
  schema: {
    type: Object,
  },
  clowmInfo: {
    type: Object as PropType<ClowmInfo>,
    required: false,
  },
  loading: {
    type: Boolean,
  },
  allowNotes: {
    type: Boolean,
  },
  viewMode: {
    type: String,
    default: "simple",
    validator(value: string) {
      return ["simple", "advanced", "expert"].includes(value);
    },
  },
});

const emit = defineEmits<{
  (
    e: "start-workflow",
    parameters: WorkflowParameters,
    metaParameters: WorkflowMetaParameters,
  ): void;
}>();

// Bootstrap Elements
// =============================================================================
let errorToast: Toast | null = null;
let parameterLoadToast: Toast | null = null;

// Types
// =============================================================================
type ParameterGroup = {
  group: string;
  title: string;
  icon?: string;
};

// JSON Schema package
// =============================================================================
const schemaCompiler = new Ajv({
  strict: false,
});

let validateSchema: ValidateFunction;

// Reactive State
// =============================================================================
const launchForm = ref<HTMLFormElement | null>(null);

const formState = reactive<{
  formInput: WorkflowParameters;
  validated: boolean;
  metaParameters: WorkflowMetaParameters;
  errorType?: string;
}>({
  formInput: {},
  validated: false,
  metaParameters: {
    logs_s3_path: undefined,
    debug_s3_path: undefined,
    provenance_s3_path: undefined,
    notes: undefined,
  },
  errorType: undefined,
});

// Computed Properties
// =============================================================================
const parameterGroups = computed<Record<string, never>>(() => {
  if (Object.keys(props.schema?.["properties"] ?? {}).length > 0) {
    return {
      ...props.schema?.["definitions"],
      ungrouped_parameters: {
        title: "Ungrouped Parameters",
        properties: props.schema?.["properties"],
        type: "object",
      },
    };
  }
  return props.schema?.["definitions"];
});

// Create a list with the names of all parameter groups
const navParameterGroups = computed<ParameterGroup[]>(() => {
  let groups = Object.keys(parameterGroups.value).map((group) => {
    return {
      group: group,
      title: parameterGroups.value[group]["title"],
      icon: parameterGroups.value[group]["fa_icon"],
    };
  });
  if (!showHidden.value) {
    groups = groups.filter(
      // filter all groups that have only hidden parameters
      (group) =>
        Object.keys(parameterGroups.value[group.group]["properties"]).filter(
          (key) =>
            !parameterGroups.value[group.group]["properties"][key]["hidden"],
        ).length > 0,
    );
  }
  if (!showOptional.value) {
    groups = groups.filter(
      // filter all groups that have no required parameter
      (group) =>
        (
          (parameterGroups.value[group.group]["required"] as Array<string>) ??
          []
        ).length > 0,
    );
  }
  return groups;
});

const showHidden = computed<boolean>(() => props.viewMode === "expert");
const showOptional = computed<boolean>(() => props.viewMode !== "simple");

// Watchers
// =============================================================================
watch(
  () => props.schema,
  (newValue) => {
    if (newValue) {
      updateSchema(newValue);
    }
  },
);

// Functions
// =============================================================================
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/ban-ts-comment, @typescript-eslint/no-unused-vars */
function updateSchema(schema: Record<string, any>) {
  validateSchema = schemaCompiler.compile(schema);
  const groupedParameters = Object.keys(parameterGroups.value).map(
    (groupName) =>
      Object.fromEntries(
        Object.entries(parameterGroups.value[groupName]["properties"]).map(
          ([parameterName, parameter]) => [
            parameterName,
            // @ts-ignore
            parameter["default"],
          ],
        ),
      ),
  );
  formState.formInput = groupedParameters.reduce((acc, val) => {
    return { ...acc, ...val };
  });
  loadParameters(executionRepository.popTemporaryParameters());
}

function startWorkflow() {
  errorToast?.hide();
  formState.validated = true;
  formState.errorType = undefined;
  // delete parameters that are strings and have a length of 0
  for (const paramName of Object.keys(formState.formInput)) {
    const param = formState.formInput[paramName];
    if (typeof param === "string" && param?.trim().length === 0) {
      delete formState.formInput[paramName];
    }
  }
  if (launchForm.value?.checkValidity()) {
    const schemaValid = validateSchema(formState.formInput);

    if (!schemaValid) {
      console.error(validateSchema.errors);
      errorToast?.show();
    } else {
      emit("start-workflow", formState.formInput, formState.metaParameters);
    }
  } else {
    formState.errorType = "form";
    errorToast?.show();
  }
}

function loadParameters(tempParams?: TemporaryParams) {
  if (tempParams) {
    for (const param in tempParams.params) {
      if (param in formState.formInput) {
        formState.formInput[param] = tempParams.params[param];
      }
    }
    formState.metaParameters = tempParams.metaParams;
    if (Object.keys(tempParams?.params ?? {}).length > 0) {
      parameterLoadToast?.show();
    }
  }
}

function scroll(selectedAnchor: string) {
  document.querySelector(selectedAnchor)?.scrollIntoView({
    behavior: "smooth",
  });
}

// Lifecycle Events
// =============================================================================
onMounted(() => {
  if (props.schema) updateSchema(props.schema);
  if (props.clowmInfo?.exampleParameters)
    Tooltip.getOrCreateInstance("#exampleDataButton");
  bucketRepository.fetchBuckets();
  bucketRepository.fetchOwnPermissions();
  keyRepository.fetchS3Keys();
  resourceRepository.fetchPublicResources();
  errorToast = new Toast("#workflowExecutionErrorToast");
  parameterLoadToast = new Toast("#workflowExecutionParameterLoadToast");
});
</script>

<template>
  <bootstrap-toast
    toast-id="workflowExecutionParameterLoadToast"
    color-class="success"
  >
    Successfully loaded parameters
  </bootstrap-toast>
  <bootstrap-toast toast-id="workflowExecutionErrorToast" color-class="danger">
    <template #default>Error starting workflow</template>
    <template #body>
      <template v-if="formState.errorType === 'form'">
        Some inputs are not valid.
      </template>
      <template v-else>
        There was an error with starting the workflow execution. Look in the
        console for more information.
      </template>
    </template>
  </bootstrap-toast>
  <upload-parameter-file-modal
    modal-id="parameterUploadModal"
    @parameters-uploaded="
      (params: WorkflowParameters) =>
        loadParameters({
          params: params,
          metaParams: {},
        })
    "
  />
  <div class="row align-items-start">
    <form
      v-if="props.schema"
      class="col-9"
      id="launchWorkflowForm"
      ref="launchForm"
      :class="{ 'was-validated': formState.validated }"
      @submit.prevent="startWorkflow"
      novalidate
    >
      <template v-for="(group, groupName) in parameterGroups" :key="groupName">
        <parameter-group-form
          v-model="formState.formInput"
          v-if="formState.formInput"
          :parameter-group-name="groupName"
          :parameter-group="group"
          :showHidden="showHidden"
          :show-optional="showOptional"
          :resource-parameters="props.clowmInfo?.resourceParameters"
        />
      </template>
      <div class="card mb-3">
        <h3 class="card-header" id="pipelineGeneralOptions">
          <font-awesome-icon icon="fa-solid fa-gear" class="me-2" />
          Pipeline Options
        </h3>
        <div class="card-body">
          <h5 class="card-title">
            General Options about the pipeline execution
          </h5>
          <div v-if="props.allowNotes" :hidden="!showOptional">
            <code
              class="bg-secondary-subtle p-2 rounded-top border border-secondary"
              >--notes</code
            >
            <div class="input-group">
              <span
                class="input-group-text border border-secondary"
                id="pipelineNotes"
              >
                <font-awesome-icon icon="fa-solid fa-sticky-note" />
              </span>
              <textarea
                class="form-control border border-secondary"
                rows="2"
                v-model="formState.metaParameters.notes"
              />
            </div>
            <label class="mb-3" for="pipelineNotes"
              >Personal notes about the pipeline execution</label
            >
          </div>
          <div>
            <code
              class="bg-secondary-subtle p-2 rounded-top border border-secondary"
              >--logs_s3_path</code
            >
            <div class="input-group">
              <span class="input-group-text border border-secondary">
                <font-awesome-icon icon="fa-solid fa-folder" />
              </span>
              <parameter-string-input
                parameter-name="logs_s3_path"
                v-model="formState.metaParameters.logs_s3_path"
                :parameter="{
                  format: 'directory-path',
                  type: 'string',
                }"
                remove-advanced
              />
            </div>
            <label class="mb-3" for="logsS3Path">
              Directory in bucket where to save Nextflow log and reports
            </label>
          </div>
          <div>
            <code
              class="bg-secondary-subtle p-2 rounded-top border border-secondary"
              >--provenance_s3_path</code
            >
            <div class="input-group">
              <span class="input-group-text border border-secondary">
                <font-awesome-icon icon="fa-solid fa-folder" />
              </span>
              <parameter-string-input
                parameter-name="provenance_s3_path"
                v-model="formState.metaParameters.provenance_s3_path"
                :parameter="{
                  format: 'directory-path',
                  type: 'string',
                }"
                remove-advanced
              />
            </div>
            <label class="mb-3" for="provenanceS3Path">
              Directory in bucket where to save provenance information about the
              workflow execution
            </label>
          </div>
          <div :hidden="!showHidden">
            <code
              class="bg-secondary-subtle p-2 rounded-top border border-secondary"
              >--debug_s3_path</code
            >
            <div class="input-group">
              <span class="input-group-text border border-secondary">
                <font-awesome-icon icon="fa-solid fa-folder" />
              </span>
              <parameter-string-input
                parameter-name="debug_s3_path"
                v-model="formState.metaParameters.debug_s3_path"
                :parameter="{
                  format: 'directory-path',
                  type: 'string',
                }"
                remove-advanced
              />
            </div>
            <label class="mb-3" for="debugS3Path">
              Directory in bucket where to save debug information about the
              workflow execution
            </label>
          </div>
        </div>
      </div>
    </form>
    <!-- Loading card -->
    <div v-else class="col-9">
      <div class="card mb-3">
        <h2 class="card-header placeholder-glow">
          <span class="placeholder col-6"></span>
        </h2>
        <div class="card-body">
          <h5 class="card-title placeholder-glow">
            <span class="placeholder col-5"> </span>
          </h5>
          <template v-for="n in 4" :key="n">
            <div class="placeholder-glow fs-5">
              <span class="placeholder w-100"> </span>
            </div>
            <div class="mb-3 placeholder-glow">
              <span class="placeholder col-3"> </span>
            </div>
          </template>
        </div>
      </div>
    </div>
    <div
      class="col-3 sticky-top border shadow-sm rounded-1 px-0"
      style="top: 70px !important; max-height: calc(100vh - 150px)"
    >
      <h5 class="mx-3 mt-2">Parameter View</h5>
      <div class="mx-2">
        <div
          class="btn-group my-1 w-100"
          role="group"
          aria-label="Basic radio toggle button group"
        >
          <input
            type="radio"
            class="btn-check"
            name="view-mode"
            id="view-mode-simple"
            autocomplete="off"
            :checked="props.viewMode === 'simple'"
            @click="
              router.replace({
                query: { ...route.query, viewMode: 'simple' },
                hash: route.hash,
              })
            "
          />
          <label class="btn btn-outline-primary" for="view-mode-simple"
            >Simple</label
          >
          <input
            type="radio"
            class="btn-check"
            name="view-mode"
            id="view-mode-advanced"
            autocomplete="off"
            :checked="props.viewMode === 'advanced'"
            @click="
              router.replace({
                query: { ...route.query, viewMode: 'advanced' },
                hash: route.hash,
              })
            "
          />
          <label class="btn btn-outline-primary" for="view-mode-advanced"
            >Advanced</label
          >
          <input
            type="radio"
            class="btn-check"
            name="view-mode"
            id="view-mode-expert"
            autocomplete="off"
            :checked="props.viewMode === 'expert'"
            @click="
              router.replace({
                query: { ...route.query, viewMode: 'expert' },
                hash: route.hash,
              })
            "
          />
          <label class="btn btn-outline-primary" for="view-mode-expert"
            >Expert</label
          >
        </div>
      </div>
      <nav class="h-100">
        <nav v-if="props.schema" class="nav">
          <ul class="ps-0">
            <li
              class="nav-link"
              v-for="group in navParameterGroups"
              :key="group.group"
            >
              <router-link
                :to="{ hash: '#' + group.group, query: route.query }"
                replace
                @click="scroll('#' + group.group)"
              >
                <font-awesome-icon
                  :icon="group.icon"
                  v-if="group.icon"
                  class="me-2"
                />
                {{ group.title }}
              </router-link>
            </li>
            <li class="nav-link">
              <router-link
                :to="{ hash: '#pipelineGeneralOptions', query: route.query }"
                replace
                @click="scroll('#pipelineGeneralOptions')"
              >
                <font-awesome-icon icon="fa-solid fa-gear" class="me-2" />
                General Pipeline Options
              </router-link>
            </li>
          </ul>
        </nav>
        <!-- Loading nav links -->
        <div v-else class="placeholder-glow ps-3 pt-3">
          <span
            v-for="n in 5"
            :key="n"
            class="placeholder col-8 mt-2 mb-3"
          ></span>
        </div>
      </nav>
      <div class="d-grid gap-2 mb-2 px-2">
        <button
          type="button"
          class="btn btn-primary"
          v-if="props.clowmInfo?.exampleParameters"
          data-bs-toggle="tooltip"
          id="exampleDataButton"
          data-bs-title="Load example parameters/data for this workflow"
          @click="
            loadParameters({
              params: props.clowmInfo?.exampleParameters,
              metaParams: {},
            })
          "
        >
          Try it out
        </button>
        <button
          class="btn btn-primary"
          data-bs-toggle="modal"
          data-bs-target="#parameterUploadModal"
        >
          <font-awesome-icon icon="fa-solid fa-upload" class="me-2" />
          Upload Parameters
        </button>
        <button
          type="submit"
          form="launchWorkflowForm"
          class="btn btn-success btn-lg"
          :disabled="props.loading || !props.schema"
        >
          <font-awesome-icon icon="fa-solid fa-play" class="me-2" />
          Launch
        </button>
      </div>
    </div>
  </div>
</template>

<style>
.was-validated *:invalid {
  border-color: var(--bs-form-invalid-border-color) !important;
  background: var(--bs-danger-bg-subtle) !important;
}
</style>