import { storeToRefs } from "pinia";
import type { Ref } from "vue";
import { computed, ref, watch } from "vue";

import type { Creator } from "@/api/workflows";
import type { DatatypesMapperModel } from "@/components/Datatypes/model";
import { useWorkflowStores } from "@/composables/workflowStores";
import type { Steps } from "@/stores/workflowStepStore";

import {
    getDisconnectedInputs,
    getDuplicateLabels,
    getMissingMetadata,
    getUnlabeledOutputs,
    getUntypedParameters,
} from "./linting";
import type {
    DisconnectedInputState,
    DuplicateLabelState,
    MetadataLintState,
    UnlabeledOuputState,
    UntypedParameterState,
} from "./lintingTypes";
import { getUntypedWorkflowParameters, type UntypedParameters } from "./parameters";

export interface LintData {
    checkAnnotation: Ref<boolean>;
    checkAnnotationLength: Ref<boolean>;
    checkReadme: Ref<boolean>;
    checkLicense: Ref<boolean>;
    checkCreator: Ref<boolean>;
    resolvedPriorityIssues: Ref<number>;
    totalPriorityIssues: Ref<number>;
    resolvedAttributeIssues: Ref<number>;
    totalAttributeIssues: Ref<number>;
    untypedParameters: Ref<UntypedParameters | undefined>;
    untypedParameterWarnings: Ref<UntypedParameterState[]>;
    disconnectedInputs: Ref<DisconnectedInputState[]>;
    duplicateLabels: Ref<DuplicateLabelState[]>;
    unlabeledOutputs: Ref<UnlabeledOuputState[]>;
    missingMetadata: Ref<MetadataLintState[]>;
}

export function useLintData(
    workflowId: Ref<string>,
    steps: Ref<Steps>,
    datatypesMapper: Ref<DatatypesMapperModel>,
    annotation?: Ref<string | null>,
    readme?: Ref<string | null>,
    license?: Ref<string | null>,
    creator?: Ref<Creator[] | null>,
): LintData {
    const workflowStores = useWorkflowStores(workflowId);

    const untypedParameters = ref<UntypedParameters>();
    const untypedParameterWarnings = ref<UntypedParameterState[]>([]);
    const disconnectedInputs = ref<DisconnectedInputState[]>([]);
    const duplicateLabels = ref<DuplicateLabelState[]>([]);
    const unlabeledOutputs = ref<UnlabeledOuputState[]>([]);
    const missingMetadata = ref<MetadataLintState[]>([]);
    watch(
        () => [steps, datatypesMapper.value],
        () => {
            if (datatypesMapper.value) {
                untypedParameters.value = getUntypedWorkflowParameters(steps.value);
                untypedParameterWarnings.value = getUntypedParameters(untypedParameters.value);
                disconnectedInputs.value = getDisconnectedInputs(steps.value, datatypesMapper.value, workflowStores);
                duplicateLabels.value = getDuplicateLabels(steps.value, workflowStores);
                unlabeledOutputs.value = getUnlabeledOutputs(steps.value);
                missingMetadata.value = getMissingMetadata(steps.value);
            }
        },
        { immediate: true, deep: true },
    );

    const checkAnnotation = computed(() => Boolean(annotation?.value));
    const checkAnnotationLength = computed(() => Boolean(annotation?.value && annotation.value.length <= 250));
    const checkReadme = computed(() => Boolean(readme?.value));
    const checkLicense = computed(() => Boolean(license?.value));
    const checkCreator = computed(() => (creator?.value ? creator.value.length > 0 : false));

    const { stepStore } = workflowStores;
    const { hasActiveOutputs, hasInputSteps } = storeToRefs(stepStore);

    /** This computes the `LintSection`s, some of which are conditionally rendered
     * in `Lint.vue`. This is used to compute the total and resolved best practice issues.
     *
     * TODO: Maybe this could be used to decide which section is rendered in the parent component as well?
     */
    const lintingSections = computed(() => [
        {
            name: "annotation",
            exists: true,
            resolved: checkAnnotation.value,
            priority: "low",
        },
        {
            name: "annotationLength",
            exists: checkAnnotation.value && !checkAnnotationLength.value,
            resolved: false,
            priority: "low",
        },
        {
            name: "readme",
            exists: true,
            resolved: checkReadme.value,
            priority: "low",
        },
        {
            name: "creator",
            exists: true,
            resolved: checkCreator.value,
            priority: "low",
        },
        {
            name: "license",
            exists: true,
            resolved: checkLicense.value,
            priority: "low",
        },
        {
            name: "untypedParameters",
            exists: Object.keys(steps.value).length > 0,
            resolved: untypedParameterWarnings.value.length === 0,
            priority: "high",
        },
        {
            name: "disconnectedInputs",
            exists: Object.keys(steps.value).length > 0,
            resolved: disconnectedInputs.value.length === 0,
            priority: "high",
        },
        {
            name: "missingMetadata",
            exists: hasInputSteps.value,
            resolved: missingMetadata.value.length === 0,
            priority: "high",
        },
        {
            name: "duplicateLabels",
            exists: hasActiveOutputs.value,
            resolved: duplicateLabels.value.length === 0,
            priority: "high",
        },
        {
            name: "unlabeledOutputs",
            exists: hasActiveOutputs.value,
            resolved: unlabeledOutputs.value.length === 0,
            priority: "high",
        },
        {
            name: "noOutputs",
            exists: !hasActiveOutputs.value,
            resolved: false,
            priority: "high",
        },
    ]);

    const totalPriorityIssues = computed(
        () => lintingSections.value.filter((section) => section.exists && section.priority === "high").length,
    );

    const resolvedPriorityIssues = computed(
        () =>
            lintingSections.value.filter((section) => section.exists && section.resolved && section.priority === "high")
                .length,
    );

    const totalAttributeIssues = computed(
        () => lintingSections.value.filter((section) => section.exists && section.priority === "low").length,
    );

    const resolvedAttributeIssues = computed(
        () =>
            lintingSections.value.filter((section) => section.exists && section.resolved && section.priority === "low")
                .length,
    );

    return {
        checkAnnotation,
        checkAnnotationLength,
        checkReadme,
        checkLicense,
        checkCreator,
        resolvedPriorityIssues,
        totalPriorityIssues,
        resolvedAttributeIssues,
        totalAttributeIssues,
        untypedParameters,
        untypedParameterWarnings,
        disconnectedInputs,
        duplicateLabels,
        unlabeledOutputs,
        missingMetadata,
    };
}
