<template>
  <QuickNav selected="Projects" />
  <TopMessage />

  <section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
    <!-- Heading -->
    <h1 id="ViewHeading" class="text-4xl text-center font-light">
      Your Project Ideas
    </h1>

    <OnboardingDialog ref="onboardingDialog" />

    <!-- Result Tabs -->
    <div class="text-center text-slate-500 border-b border-slate-300 mt-8">
      <ul class="flex flex-wrap justify-center gap-4 -mb-px">
        <li>
          <a
            href="#"
            :class="computedOfferTabClassNames()"
            @click="
              offers = [];
              projects = [];
              showOffers = true;
              showProjects = false;
              loadOffers();
            "
          >
            Offers
          </a>
        </li>
        <li>
          <a
            href="#"
            :class="computedProjectTabClassNames()"
            @click="
              offers = [];
              projects = [];
              showOffers = false;
              showProjects = true;
              loadProjects();
            "
          >
            Projects
          </a>
        </li>
      </ul>
    </div>

    <!-- Quick Search -->
    <!--
    <div id="QuickSearch" class="mb-4 flex">
      <input
        type="text"
        placeholder="Search…"
        class="block w-full rounded-l border border-r-0 border-slate-400 px-3 py-2"
      />
      <button
        class="px-4 rounded-r bg-slate-200 border border-l-0 border-slate-400"
      >
        <font-awesome icon="magnifying-glass" class="fa-fw"></font-awesome>
      </button>
    </div>
    -->

    <!-- New Project -->
    <button
      v-if="isRegistered && showProjects"
      :class="newProjectButtonClasses"
      @click="onClickNewProject()"
    >
      <font-awesome icon="plus" class="fa-fw"></font-awesome>
    </button>

    <!-- Loading Animation -->
    <div v-if="isLoading" :class="loadingAnimationClasses">
      <font-awesome icon="spinner" class="fa-spin-pulse"></font-awesome>
    </div>

    <!-- Offer Results List -->
    <InfiniteScroll v-if="showOffers" @reached-bottom="loadMoreOfferData">
      <div v-if="offers.length === 0" class="text-center py-4">
        You have not offered anything.
        <br />
        <router-link to="/discover" class="text-blue-600">
          Look for projects worth some of your time.
        </router-link>
      </div>
      <ul id="listOffers" class="border-t border-slate-300">
        <li
          v-for="offer in offers"
          :key="offer.handleId"
          class="border-b border-slate-300"
        >
          <div class="block py-4 flex gap-4">
            <div v-if="offer.fulfillsPlanHandleId" class="flex-none">
              <ProjectIcon
                :entity-id="offer.fulfillsPlanHandleId"
                :icon-size="48"
                :class="projectIconClasses"
              />
            </div>
            <div v-if="offer.recipientDid" class="flex-none w-12">
              <EntityIcon
                :entity-id="offer.recipientDid"
                :icon-size="48"
                :class="entityIconClasses"
              />
            </div>

            <div>
              <div>
                To
                {{
                  offer.fulfillsPlanHandleId
                    ? projectNameFromHandleId[offer.fulfillsPlanHandleId]
                    : didInfo(
                        offer.recipientDid,
                        activeDid,
                        allMyDids,
                        allContacts,
                      )
                }}
              </div>
              <div>
                {{ offer.objectDescription }}
              </div>

              <span class="text-sm">
                <span v-if="offer.amount">
                  <font-awesome
                    :icon="iconForUnitCode(offer.unit)"
                    class="fa-fw text-slate-400"
                  />

                  <span v-if="offer.amountGiven >= offer.amount">
                    <font-awesome
                      icon="check-circle"
                      class="fa-fw text-green-500"
                    />
                    All {{ offer.amount }} given
                  </span>
                  <span v-else>
                    <font-awesome
                      icon="triangle-exclamation"
                      class="fa-fw text-yellow-500"
                    />
                    {{ offer.amountGiven ? "" : "All" }}
                    {{ offer.amount - (offer.amountGiven || 0) }} remaining
                  </span>

                  <span v-if="offer.amountGiven > 0">
                    <span class="text-sm text-slate-400">
                      ({{ offer.amountGiven }} given,
                      <span
                        v-if="offer.amountGivenConfirmed >= offer.amountGiven"
                      >
                        <!--
                         There's no need for a green icon:
                         it's unnecessary if there's already a green, and confusing if there's a 
                         yellow.
                        -->
                        all
                      </span>
                      <span v-else>
                        <!-- only show icon if there's not already a warning -->
                        <font-awesome
                          v-if="offer.amountGiven >= offer.amount"
                          icon="triangle-exclamation"
                          class="fa-fw text-yellow-300"
                        />
                        {{ offer.amountGivenConfirmed || 0 }}
                      </span>
                      of that is confirmed)
                    </span>
                  </span>
                </span>
                <span v-else>
                  <!-- Non-amount offer -->
                  <span v-if="offer.nonAmountGivenConfirmed">
                    <font-awesome
                      icon="check-circle"
                      class="fa-fw text-green-500"
                    />
                    {{ offer.nonAmountGivenConfirmed }}
                    {{ offer.nonAmountGivenConfirmed == 1 ? "give" : "gives" }}
                    are confirmed.
                  </span>
                  <span v-else>
                    <font-awesome
                      icon="triangle-exclamation"
                      class="fa-fw text-yellow-500"
                    />
                    <span class="text-sm">Not confirmed by anyone</span>
                  </span>
                </span>

                <a @click="onClickLoadClaim(offer.jwtId)">
                  <font-awesome
                    icon="file-lines"
                    class="pl-2 text-blue-500 cursor-pointer"
                  ></font-awesome>
                </a>
              </span>
            </div>
          </div>
        </li>
      </ul>
    </InfiniteScroll>

    <!-- Project Results List -->
    <InfiniteScroll v-if="showProjects" @reached-bottom="loadMoreProjectData">
      <div v-if="projects.length === 0" class="text-center py-4">
        You have not announced any projects.
        <div v-if="isRegistered">
          Hit the big
          <font-awesome icon="plus" :class="plusIconClasses" />
          button. You'll never know until you try.
        </div>
        <div v-else>
          <button
            :class="onboardingButtonClasses"
            @click="showNameThenIdDialog()"
          >
            Get someone to onboard you.
          </button>
          <UserNameDialog ref="userNameDialog" />
        </div>
      </div>
      <ul id="listProjects" class="border-t border-slate-300">
        <li
          v-for="project in projects"
          :key="project.handleId"
          class="border-b border-slate-300"
        >
          <a
            class="block py-4 flex gap-4 cursor-pointer"
            @click="onClickLoadProject(project.handleId)"
          >
            <div class="flex-none">
              <ProjectIcon
                :entity-id="project.handleId"
                :icon-size="48"
                :image-url="project.image"
                :class="projectIconClasses"
              />
            </div>

            <div class="grow overflow-hidden">
              <h2 class="text-base font-semibold">
                {{ project.name || "Unnamed Project" }}
              </h2>
              <div class="text-sm truncate">
                {{ project.description }}
              </div>
            </div>
          </a>
        </li>
      </ul>
    </InfiniteScroll>
  </section>
</template>

<script lang="ts">
import { AxiosRequestConfig } from "axios";
import { Component, Vue } from "vue-facing-decorator";
import { Router } from "vue-router";
import { Capacitor } from "@capacitor/core";

import { NotificationIface } from "../constants/app";
import EntityIcon from "../components/EntityIcon.vue";
import InfiniteScroll from "../components/InfiniteScroll.vue";
import QuickNav from "../components/QuickNav.vue";
import OnboardingDialog from "../components/OnboardingDialog.vue";
import ProjectIcon from "../components/ProjectIcon.vue";
import TopMessage from "../components/TopMessage.vue";
import UserNameDialog from "../components/UserNameDialog.vue";
import { Contact } from "../db/tables/contacts";
import { didInfo, getHeaders, getPlanFromCache } from "../libs/endorserServer";
import { OfferSummaryRecord, PlanData } from "../interfaces/records";
import { OnboardPage, iconForUnitCode } from "../libs/util";
import { logger } from "../utils/logger";
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify";
import {
  NOTIFY_NO_ACCOUNT_ERROR,
  NOTIFY_PROJECT_LOAD_ERROR,
  NOTIFY_PROJECT_INIT_ERROR,
  NOTIFY_OFFERS_LOAD_ERROR,
  NOTIFY_OFFERS_FETCH_ERROR,
  NOTIFY_CAMERA_SHARE_METHOD,
} from "@/constants/notifications";

/**
 * Projects View Component
 *
 * Main dashboard for managing user projects and offers within the TimeSafari platform.
 * Provides dual-mode interface for viewing:
 * - Personal projects: Ideas and plans created by the user
 * - Active offers: Commitments made to help with other projects
 *
 * Key Features:
 * - Infinite scrolling for large datasets
 * - Project creation and navigation
 * - Offer tracking with confirmation status
 * - Onboarding integration for new users
 * - Cross-platform compatibility (web, mobile, desktop)
 *
 * Security: All API calls are authenticated using user's DID
 * Privacy: Only user's own projects and offers are displayed
 */
@Component({
  name: "ProjectsView",
  components: {
    EntityIcon,
    InfiniteScroll,
    QuickNav,
    OnboardingDialog,
    ProjectIcon,
    TopMessage,
    UserNameDialog,
  },
  mixins: [PlatformServiceMixin],
})
export default class ProjectsView extends Vue {
  $notify!: (notification: NotificationIface, timeout?: number) => void;
  $router!: Router;

  notify!: ReturnType<typeof createNotifyHelpers>;

  // User account state
  activeDid = "";
  allContacts: Array<Contact> = [];
  allMyDids: Array<string> = [];
  apiServer = "";
  givenName = "";
  isLoading = false;
  isRegistered = false;

  // Data collections
  offers: OfferSummaryRecord[] = [];
  projectNameFromHandleId: Record<string, string> = {}; // mapping from handleId to description
  projects: PlanData[] = [];

  // UI state
  showOffers = false;
  showProjects = true;

  // Utility imports
  didInfo = didInfo;
  iconForUnitCode = iconForUnitCode;

  /**
   * Initializes notification helpers
   */
  created() {
    this.notify = createNotifyHelpers(this.$notify);
  }

  /**
   * Component initialization
   *
   * Workflow:
   * 1. Load user settings and account information
   * 2. Load contacts for displaying offer recipients
   * 3. Initialize onboarding dialog if needed
   * 4. Load initial project data
   *
   * Error handling: Shows appropriate user messages for different failure scenarios
   */
  async mounted() {
    try {
      await this.initializeUserSettings();
      await this.loadContactsData();
      await this.initializeUserIdentities();
      await this.checkOnboardingStatus();
      await this.loadInitialData();
    } catch (err) {
      logger.error("Error initializing ProjectsView:", err);
      this.notify.error(NOTIFY_PROJECT_INIT_ERROR.message, TIMEOUTS.LONG);
    }
  }

  /**
   * Loads user settings from active account
   */
  private async initializeUserSettings() {
    const settings = await this.$accountSettings();
    this.activeDid = settings.activeDid || "";
    this.apiServer = settings.apiServer || "";
    this.isRegistered = !!settings.isRegistered;
    this.givenName = settings.firstName || "";
  }

  /**
   * Loads contacts data for displaying offer recipients
   */
  private async loadContactsData() {
    this.allContacts = await this.$contacts();
  }

  /**
   * Initializes user identity information
   */
  private async initializeUserIdentities() {
    this.allMyDids = await this.$getAllAccountDids();
  }

  /**
   * Checks if onboarding dialog should be shown
   */
  private async checkOnboardingStatus() {
    const settings = await this.$accountSettings();
    if (!settings.finishedOnboarding) {
      (this.$refs.onboardingDialog as OnboardingDialog).open(
        OnboardPage.Create,
      );
    }
  }

  /**
   * Loads initial project data if user has valid account
   */
  private async loadInitialData() {
    if (this.allMyDids.length === 0) {
      logger.error("No accounts found for user");
      this.notify.error(NOTIFY_NO_ACCOUNT_ERROR.message, TIMEOUTS.LONG);
    } else {
      await this.loadProjects();
    }
  }

  /**
   * Core project data loader
   *
   * Fetches project data from the endorser server and populates the projects array.
   * Handles authentication, error scenarios, and loading states.
   *
   * @param url - The API endpoint URL for fetching project data
   */
  async projectDataLoader(url: string) {
    try {
      const headers = await getHeaders(this.activeDid, this.$notify);
      this.isLoading = true;
      const resp = await this.axios.get(url, { headers } as AxiosRequestConfig);

      if (resp.status === 200 && resp.data.data) {
        const plans: PlanData[] = resp.data.data;
        for (const plan of plans) {
          const { name, description, handleId, image, issuerDid, rowId } = plan;
          this.projects.push({
            name,
            description,
            image,
            handleId,
            issuerDid,
            rowId,
          });
        }
      } else {
        logger.error(
          "Bad server response & data for plans:",
          resp.status,
          resp.data,
        );
        this.notify.error(NOTIFY_PROJECT_LOAD_ERROR.message, TIMEOUTS.LONG);
      }
    } catch (error: any) {
      logger.error("Got error loading plans:", error.message || error);
      this.notify.error(NOTIFY_PROJECT_LOAD_ERROR.message, TIMEOUTS.LONG);
    } finally {
      this.isLoading = false;
    }
  }

  /**
   * Data loader used by infinite scroller
   *
   * Implements pagination by loading additional projects when user scrolls to bottom.
   * Uses the last project's rowId as a cursor for the next batch.
   *
   * @param payload - Flag from InfiniteScroll component indicating if more data should be loaded
   */
  async loadMoreProjectData(payload: boolean) {
    if (this.projects.length > 0 && payload) {
      const latestProject = this.projects[this.projects.length - 1];
      await this.loadProjects(`beforeId=${latestProject.rowId}`);
    }
  }

  /**
   * Load projects initially or with pagination
   *
   * Constructs the API URL for fetching user's projects and delegates to projectDataLoader.
   *
   * @param urlExtra - Additional URL parameters for pagination (e.g., "beforeId=123")
   */
  async loadProjects(urlExtra: string = "") {
    const url = `${this.apiServer}/api/v2/report/plansByIssuer?${urlExtra}`;
    await this.projectDataLoader(url);
  }

  /**
   * Handle clicking on a project entry
   *
   * Navigates to the detailed project view for the selected project.
   *
   * @param id - The unique identifier of the project to view
   */
  onClickLoadProject(id: string) {
    const route = {
      path: "/project/" + encodeURIComponent(id),
    };
    this.$router.push(route);
  }

  /**
   * Handle clicking on the new project button
   *
   * Navigates to the project creation/editing interface.
   */
  onClickNewProject(): void {
    const route = {
      name: "new-edit-project",
    };
    this.$router.push(route);
  }

  /**
   * Handle clicking on a claim/offer link
   *
   * Navigates to the detailed claim view for the selected offer.
   *
   * @param jwtId - The JWT identifier of the claim to view
   */
  onClickLoadClaim(jwtId: string) {
    const route = {
      path: "/claim/" + encodeURIComponent(jwtId),
    };
    this.$router.push(route);
  }

  /**
   * Core offer data loader
   *
   * Fetches offer data from the endorser server and populates the offers array.
   * Also retrieves associated project names for display purposes.
   *
   * @param url - The API endpoint URL for fetching offer data
   */
  async offerDataLoader(url: string) {
    const headers = await getHeaders(this.activeDid);

    try {
      this.isLoading = true;
      const resp = await this.axios.get(url, { headers } as AxiosRequestConfig);

      if (resp.status === 200 && resp.data.data) {
        // Process offers one-by-one to retrieve project names from server cache
        for (const offer of resp.data.data) {
          if (offer.fulfillsPlanHandleId) {
            const project = await getPlanFromCache(
              offer.fulfillsPlanHandleId,
              this.axios,
              this.apiServer,
              this.activeDid,
            );
            const projectName = project?.name as string;
            this.projectNameFromHandleId[offer.fulfillsPlanHandleId] =
              projectName;
          }
          this.offers = this.offers.concat([offer]);
        }
      } else {
        logger.error(
          "Bad server response & data for offers:",
          resp.status,
          resp.data,
        );
        this.notify.error(NOTIFY_OFFERS_LOAD_ERROR.message, TIMEOUTS.LONG);
      }
    } catch (error: any) {
      logger.error("Got error loading offers:", error.message || error);
      this.notify.error(NOTIFY_OFFERS_FETCH_ERROR.message, TIMEOUTS.LONG);
    } finally {
      this.isLoading = false;
    }
  }

  /**
   * Data loader used by infinite scroller for offers
   *
   * Implements pagination by loading additional offers when user scrolls to bottom.
   * Uses the last offer's jwtId as a cursor for the next batch.
   *
   * @param payload - Flag from InfiniteScroll component indicating if more data should be loaded
   */
  async loadMoreOfferData(payload: boolean) {
    if (this.offers.length > 0 && payload) {
      const latestOffer = this.offers[this.offers.length - 1];
      await this.loadOffers(`&beforeId=${latestOffer.jwtId}`);
    }
  }

  /**
   * Load offers initially or with pagination
   *
   * Constructs the API URL for fetching user's offers and delegates to offerDataLoader.
   *
   * @param urlExtra - Additional URL parameters for pagination (e.g., "&beforeId=123")
   */
  async loadOffers(urlExtra: string = "") {
    const url = `${this.apiServer}/api/v2/report/offers?offeredByDid=${this.activeDid}${urlExtra}`;
    await this.offerDataLoader(url);
  }

  /**
   * Shows name dialog if needed, then prompts for share method
   *
   * Ensures user has provided their name before proceeding with contact sharing.
   * Uses UserNameDialog component if name is not set.
   */
  showNameThenIdDialog() {
    if (!this.givenName) {
      (this.$refs.userNameDialog as UserNameDialog).open(() => {
        this.promptForShareMethod();
      });
    } else {
      this.promptForShareMethod();
    }
  }

  /**
   * Prompts user to choose contact sharing method
   *
   * Presents modal dialog asking if users are nearby with cameras.
   * Routes to appropriate sharing method based on user's choice:
   * - QR code sharing for nearby users with cameras
   * - Alternative sharing methods for remote users
   */
  promptForShareMethod() {
    this.$notify(
      {
        group: "modal",
        type: "confirm",
        title: NOTIFY_CAMERA_SHARE_METHOD.title,
        text: NOTIFY_CAMERA_SHARE_METHOD.text,
        onYes: () => this.handleQRCodeClick(),
        onNo: () => this.$router.push({ name: "share-my-contact-info" }),
        yesText: NOTIFY_CAMERA_SHARE_METHOD.yesText,
        noText: NOTIFY_CAMERA_SHARE_METHOD.noText,
      },
      TIMEOUTS.MODAL,
    );
  }

  /**
   * Computed properties for template logic streamlining
   */

  /**
   * CSS class names for offer tab styling
   * @returns Object with CSS classes based on current tab selection
   */
  get offerTabClasses() {
    return {
      "inline-block": true,
      "py-3": true,
      "rounded-t-lg": true,
      "border-b-2": true,
      active: this.showOffers,
      "text-black": this.showOffers,
      "border-black": this.showOffers,
      "font-semibold": this.showOffers,
      "text-blue-600": !this.showOffers,
      "border-transparent": !this.showOffers,
      "hover:border-slate-400": !this.showOffers,
    };
  }

  /**
   * CSS class names for new project button
   * @returns String with CSS classes for the floating new project button
   */
  get newProjectButtonClasses() {
    return "fixed right-6 top-24 text-center text-4xl leading-none bg-green-600 text-white w-14 py-2.5 rounded-full";
  }

  /**
   * CSS class names for loading animation
   * @returns String with CSS classes for the loading spinner
   */
  get loadingAnimationClasses() {
    return "fixed left-6 bottom-24 text-center text-4xl leading-none bg-slate-400 text-white w-14 py-2.5 rounded-full";
  }

  /**
   * CSS class names for project icon
   * @returns String with CSS classes for project icons
   */
  get projectIconClasses() {
    return "inline-block align-middle border border-slate-300 rounded-md max-h-12 max-w-12";
  }

  /**
   * CSS class names for entity icon
   * @returns String with CSS classes for entity icons
   */
  get entityIconClasses() {
    return "inline-block align-middle border border-slate-300 rounded-md";
  }

  /**
   * CSS class names for plus icon in empty state
   * @returns String with CSS classes for the plus icon
   */
  get plusIconClasses() {
    return "bg-green-600 text-white px-1.5 py-1 rounded-full";
  }

  /**
   * CSS class names for onboarding button
   * @returns String with CSS classes for the onboarding button
   */
  get onboardingButtonClasses() {
    return "text-md font-bold bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white mt-2 px-2 py-3 rounded-md";
  }

  /**
   * CSS class names for project tab styling
   * @returns Object with CSS classes based on current tab selection
   */
  get projectTabClasses() {
    return {
      "inline-block": true,
      "py-3": true,
      "rounded-t-lg": true,
      "border-b-2": true,
      active: this.showProjects,
      "text-black": this.showProjects,
      "border-black": this.showProjects,
      "font-semibold": this.showProjects,
      "text-blue-600": !this.showProjects,
      "border-transparent": !this.showProjects,
      "hover:border-slate-400": !this.showProjects,
    };
  }

  /**
   * Utility methods
   */

  /**
   * Handles QR code sharing functionality with platform detection
   *
   * Routes to appropriate QR code interface based on current platform:
   * - Full QR scanner for native mobile platforms
   * - Web-based QR interface for browser environments
   */
  private handleQRCodeClick() {
    if (Capacitor.isNativePlatform()) {
      this.$router.push({ name: "contact-qr-scan-full" });
    } else {
      this.$router.push({ name: "contact-qr" });
    }
  }

  /**
   * Legacy method compatibility
   * @deprecated Use computedOfferTabClassNames for backward compatibility
   */
  public computedOfferTabClassNames() {
    return this.offerTabClasses;
  }

  /**
   * Legacy method compatibility
   * @deprecated Use computedProjectTabClassNames for backward compatibility
   */
  public computedProjectTabClassNames() {
    return this.projectTabClasses;
  }
}
</script>
