import type { LearningPath } from "@/services/learningPath";
import type {
  CircleChild,
  CircleGoal,
  CircleJobSituation,
  CourseCompletion,
  LearningContent,
  LearningContentInterface,
  LearningSequence,
  LearningUnit,
  LearningUnitPerformanceCriteria,
  WagtailCircle,
} from "@/types";
import groupBy from "lodash/groupBy";
import partition from "lodash/partition";
import values from "lodash/values";
import log from "loglevel";

function isLearningContentType(object: any): object is LearningContent {
  return (
    object?.content_type === "learnpath.LearningContentAssignment" ||
    object?.content_type === "learnpath.LearningContentAttendanceCourse" ||
    object?.content_type === "learnpath.LearningContentDocumentList" ||
    object?.content_type === "learnpath.LearningContentFeedback" ||
    object?.content_type === "learnpath.LearningContentLearningModule" ||
    object?.content_type === "learnpath.LearningContentMediaLibrary" ||
    object?.content_type === "learnpath.LearningContentPlaceholder" ||
    object?.content_type === "learnpath.LearningContentRichText" ||
    object?.content_type === "learnpath.LearningContentTest" ||
    object?.content_type === "learnpath.LearningContentVideo"
  );
}

export function parseLearningSequences(
  circle: Circle,
  children: CircleChild[]
): LearningSequence[] {
  let learningSequence: LearningSequence | undefined;
  let learningUnit: LearningUnit | undefined;
  let learningContent: LearningContent | undefined;
  let previousLearningContent: LearningContent | undefined;
  const result: LearningSequence[] = [];

  children.forEach((child) => {
    if (child.content_type === "learnpath.LearningSequence") {
      if (learningSequence) {
        if (learningUnit) {
          learningUnit.last = true;
        }
      }
      learningSequence = Object.assign(child, { learningUnits: [] });
      result.push(learningSequence);
    } else if (child.content_type === "learnpath.LearningUnit") {
      if (!learningSequence) {
        throw new Error("LearningUnit found before LearningSequence");
      }

      learningUnit = Object.assign(child, {
        learningContents: [],
        parentLearningSequence: learningSequence,
        parentCircle: circle,
        children: child.children.map((c) => {
          c.parentLearningUnit = learningUnit;
          c.parentLearningSequence = learningSequence;
          return c;
        }),
      });
      learningSequence.learningUnits.push(learningUnit);
    } else if (isLearningContentType(child)) {
      if (!learningUnit) {
        throw new Error(`LearningContent found before LearningUnit ${child.slug}`);
      }
      previousLearningContent = learningContent;

      learningContent = Object.assign(child, {
        parentCircle: circle,
        parentLearningSequence: learningSequence,
        parentLearningUnit: learningUnit,
        previousLearningContent: previousLearningContent,
      });

      if (previousLearningContent) {
        previousLearningContent.nextLearningContent = learningContent;
      }

      learningUnit.learningContents.push(child);
    } else {
      log.error("Unknown CircleChild found...", child);
      throw new Error("Unknown CircleChild found...");
    }
  });

  if (learningUnit) {
    learningUnit.last = true;
  } else {
    throw new Error(
      "Finished with LearningContent but there is no LearningSequence and LearningUnit"
    );
  }

  // sum minutes
  result.forEach((learningSequence) => {
    learningSequence.minutes = 0;
    learningSequence.learningUnits.forEach((learningUnit) => {
      learningUnit.minutes = 0;
      learningUnit.learningContents.forEach((learningContent) => {
        learningUnit.minutes += learningContent.minutes;
      });
      learningSequence.minutes += learningUnit.minutes;
    });
  });

  return result;
}

export class Circle implements WagtailCircle {
  readonly content_type = "learnpath.Circle";
  readonly learningSequences: LearningSequence[];

  nextCircle?: Circle;
  previousCircle?: Circle;

  constructor(
    public readonly id: number,
    public readonly slug: string,
    public readonly title: string,
    public readonly translation_key: string,
    public readonly frontend_url: string,
    public readonly description: string,
    public readonly children: CircleChild[],
    public readonly goal_description: string,
    public readonly goals: CircleGoal[],
    public readonly job_situation_description: string,
    public readonly job_situations: CircleJobSituation[],
    public readonly parentLearningPath?: LearningPath
  ) {
    this.learningSequences = parseLearningSequences(this, this.children);
  }

  public static fromJson(
    wagtailCircle: WagtailCircle,
    learningPath?: LearningPath
  ): Circle {
    // TODO add error checking when the data does not conform to the schema
    return new Circle(
      wagtailCircle.id,
      wagtailCircle.slug,
      wagtailCircle.title,
      wagtailCircle.translation_key,
      wagtailCircle.frontend_url,
      wagtailCircle.description,
      wagtailCircle.children,
      wagtailCircle.goal_description,
      wagtailCircle.goals,
      wagtailCircle.job_situation_description,
      wagtailCircle.job_situations,
      learningPath
    );
  }

  public get flatChildren(): (
    | LearningContentInterface
    | LearningUnitPerformanceCriteria
  )[] {
    const result: (LearningContentInterface | LearningUnitPerformanceCriteria)[] = [];
    this.learningSequences.forEach((learningSequence) => {
      learningSequence.learningUnits.forEach((learningUnit) => {
        learningUnit.children.forEach((performanceCriteria) => {
          result.push(performanceCriteria);
        });
        learningUnit.learningContents.forEach((learningContent) => {
          result.push(learningContent);
        });
      });
    });
    return result;
  }

  public get flatLearningContents(): LearningContent[] {
    const result: LearningContent[] = [];
    this.learningSequences.forEach((learningSequence) => {
      learningSequence.learningUnits.forEach((learningUnit) => {
        learningUnit.learningContents.forEach((learningContent) => {
          result.push(learningContent);
        });
      });
    });
    return result;
  }

  public get flatLearningUnits(): LearningUnit[] {
    const result: LearningUnit[] = [];
    this.learningSequences.forEach((learningSequence) => {
      learningSequence.learningUnits.forEach((learningUnit) => {
        result.push(learningUnit);
      });
    });
    return result;
  }

  public someFinishedInLearningSequence(translationKey: string): boolean {
    if (translationKey) {
      return (
        this.flatChildren.filter((lc) => {
          return (
            lc.completion_status === "success" &&
            lc.parentLearningSequence?.translation_key === translationKey
          );
        }).length > 0
      );
    }

    return false;
  }

  public allFinishedInLearningSequence(translationKey: string): boolean {
    if (translationKey) {
      const [performanceCriteria, learningContents] = partition(
        this.flatChildren.filter(
          (lc) => lc.parentLearningSequence?.translation_key === translationKey
        ),
        function (child) {
          return child.content_type === "competence.PerformanceCriteria";
        }
      );

      const groupedPerformanceCriteria = values(
        groupBy(performanceCriteria, (pc) => pc.parentLearningUnit?.id)
      );

      return (
        learningContents.every((lc) => lc.completion_status === "success") &&
        (groupedPerformanceCriteria.length === 0 ||
          groupedPerformanceCriteria.every((group) =>
            group.every(
              (pc) =>
                pc.completion_status === "success" || pc.completion_status === "fail"
            )
          ))
      );
    }

    return false;
  }

  public isComplete(): boolean {
    return this.learningSequences.every((ls) =>
      this.allFinishedInLearningSequence(ls.translation_key)
    );
  }

  public parseCompletionData(completionData: CourseCompletion[]) {
    this.flatChildren.forEach((page) => {
      const pageIndex = completionData.findIndex((e) => {
        return e.page_key === page.translation_key;
      });
      if (pageIndex >= 0) {
        page.completion_status = completionData[pageIndex].completion_status;
      } else {
        page.completion_status = "unknown";
      }
    });

    if (this.parentLearningPath) {
      this.parentLearningPath.calcNextLearningContent(completionData);
    }
  }
}
