<script lang="ts">
  import type { Moment } from "moment";
  import { get } from "svelte/store";
  import { isNotVoid } from "typed-assert";

  import { getObsidianContext } from "../../context/obsidian-context";
  import { currentTimeSignal, isToday } from "../../global-store/current-time";
  import { getVisibleHours, snap } from "../../global-store/derived-settings";
  import { selectLogTimeBlocksForDay } from "../../redux";
  import { selectLogEntriesById } from "../../redux/index/index-slice";
  import type { LogTimeBlock } from "../../time-block-types";
  import {
    getPointerOffsetY,
    isTouchEvent,
    offsetYToMinutes,
  } from "../../util/dom";
  import { minutesToMomentOfDay } from "../../util/moment";
  import { getBlockProps, getDayKey } from "../../util/time-block-utils";
  import { createGestures } from "../actions/gestures";
  import { createActiveClockMenu } from "../active-clock-menu";
  import { createCompletedClockMenu } from "../completed-clock-menu";

  import Column from "./column.svelte";
  import LocalTimeBlock from "./local-time-block.svelte";
  import NeedleClockControl from "./needle-clock-control.svelte";
  import Needle from "./needle.svelte";
  import PositionedTimeBlock from "./positioned-time-block.svelte";
  import Selectable from "./selectable.svelte";
  import UnscheduledTimeBlock from "./unscheduled-time-block.svelte";

  const {
    day,
    isUnderCursor = false,
  }: { day: Moment; isUnderCursor?: boolean } = $props();

  const {
    settingsStore,
    editContext: {
      confirmEdit,
      handlers: { handleContainerMouseDown },
      getDisplayedTimeBlocksForTimeline,
      editOperation,
    },
    pointerDateTime,
    settingsSignal,
    useSelector,
    logEntryEditor,
    workspaceFacade,
    openLogEntryEditModal,
  } = getObsidianContext();

  const displayedTimeBlocksForTimeline = $derived(
    getDisplayedTimeBlocksForTimeline(day),
  );
  const dayKey = $derived(getDayKey(day));

  const logEntriesForDay = useSelector((state) =>
    selectLogTimeBlocksForDay(state, dayKey, currentTimeSignal.current),
  );
  const logEntriesById = useSelector(selectLogEntriesById);

  // todo: separate LogTimeBlockView (clamped) & LogTimeBlock
  function showLogBlockMenu(
    event: MouseEvent | PointerEvent | TouchEvent,
    timeBlockView: LogTimeBlock,
  ) {
    const logEntry = logEntriesById.current[timeBlockView.id];

    isNotVoid(logEntry, `No log entry found for block id ${timeBlockView.id}`);

    const isCompleted = logEntry.end;

    if (isCompleted) {
      createCompletedClockMenu({
        event,
        timeBlock: timeBlockView,
        logEntry: logEntry,
        logEntryEditor,
        workspaceFacade,
        openLogEntryEditModal,
      });
    } else {
      createActiveClockMenu({
        event,
        timeBlock: timeBlockView,
        logEntryEditor,
        workspaceFacade,
        // pass the raw entry so "Edit..." targets the real (unclamped) entry
        openLogEntryEditModal: (timeBlock) =>
          openLogEntryEditModal(timeBlock, logEntry),
      });
    }
  }

  let el: HTMLElement | undefined = $state();

  function updatePointerDateTime(event: MouseEvent | TouchEvent) {
    isNotVoid(el);

    const newOffsetY = snap(getPointerOffsetY(el, event), $settingsStore);
    const minutesSinceMidnight = offsetYToMinutes(
      newOffsetY,
      settingsSignal.current.zoomLevel,
      settingsSignal.current.startHour,
    );
    const dateTime = minutesToMomentOfDay(
      minutesSinceMidnight,
      window.moment(day),
    );
    const previousDateTime = get(pointerDateTime).dateTime;

    if (!dateTime.isSame(previousDateTime, "minute")) {
      pointerDateTime.set({ dateTime, type: "dateTime" });
    }
  }

  function handleContainerPointerDown(event: MouseEvent | TouchEvent) {
    updatePointerDateTime(event);
    handleContainerMouseDown();
  }

  function handleContainerPointerMove(event: MouseEvent | TouchEvent) {
    if (get(editOperation)) {
      updatePointerDateTime(event);
    }
  }

  const timelineGestures = createGestures({
    onlongpress: (event) => {
      if (event.target !== el) {
        return;
      }

      handleContainerPointerDown(event);
    },
    onpanmove: handleContainerPointerMove,
    onpanend: confirmEdit,
    options: { mouseSupport: false },
  });
</script>

<div class="timeline">
  {#if $settingsStore.timelineColumns.planner}
    <Column visibleHours={getVisibleHours($settingsStore)}>
      {#if $isToday(day)}
        <Needle autoScrollBlocked={isUnderCursor} />
      {/if}

      <div
        bind:this={el}
        class="tasks absolute-stretch-x"
        onpointerdown={(event) => {
          if (isTouchEvent(event) || event.target !== el) {
            return;
          }

          handleContainerPointerDown(event);
        }}
        onpointermove={handleContainerPointerMove}
        onpointerup={confirmEdit}
        use:timelineGestures
      >
        {#each $displayedTimeBlocksForTimeline.withTime as timeBlock (timeBlock.id)}
          <PositionedTimeBlock {timeBlock}>
            <UnscheduledTimeBlock {timeBlock}>
              {#snippet bottomDecoration()}
                {getBlockProps(timeBlock, settingsSignal.current)}
              {/snippet}
            </UnscheduledTimeBlock>
          </PositionedTimeBlock>
        {/each}
      </div>
    </Column>
  {/if}

  {#if $settingsStore.timelineColumns.timeTracker}
    <Column visibleHours={getVisibleHours($settingsStore)}>
      {#if $isToday(day)}
        <Needle autoScrollBlocked={isUnderCursor}>
          {#snippet controls()}
            <NeedleClockControl />
          {/snippet}
        </Needle>
      {/if}

      <div class="tasks absolute-stretch-x">
        {#each logEntriesForDay.current as timeBlock (timeBlock.id)}
          <PositionedTimeBlock {timeBlock}>
            <Selectable
              onSecondarySelect={(event) => showLogBlockMenu(event, timeBlock)}
            >
              {#snippet children({ use, onpointerup, state })}
                <LocalTimeBlock
                  isActive={state === "secondary"}
                  {onpointerup}
                  {timeBlock}
                  {use}
                >
                  {#snippet bottomDecoration()}
                    {getBlockProps(timeBlock, settingsSignal.current)}
                  {/snippet}
                </LocalTimeBlock>
              {/snippet}
            </Selectable>
          </PositionedTimeBlock>
        {/each}
      </div>
    </Column>
  {/if}
</div>

<style>
  .timeline {
    display: flex;
    flex: 1 1 0;
    height: fit-content;
    border-inline-end: var(--timeline-border-inline-end);
  }

  .tasks {
    top: 0;
    bottom: 0;

    display: flex;
    flex-direction: column;

    margin-inline: var(--size-4-2);
  }

  .tasks :global(.planner-sticky-block-content) {
    position: sticky;
    top: 0;
    max-height: 100%;
  }
</style>
