<!--
// Copyright © 2020 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
  import { AttachmentStyledBox } from '@hcengineering/attachment-resources'
  import contact, { Organization } from '@hcengineering/contact'
  import { AccountArrayEditor, UserBox } from '@hcengineering/contact-resources'
  import core, {
    Account,
    AttachedData,
    Data,
    Ref,
    Role,
    RolesAssignment,
    SortingOrder,
    fillDefaults,
    generateId,
    getCurrentAccount,
    makeCollabId
  } from '@hcengineering/core'
  import { getEmbeddedLabel } from '@hcengineering/platform'
  import {
    Card,
    InlineAttributeBar,
    MessageBox,
    createMarkup,
    createQuery,
    getClient
  } from '@hcengineering/presentation'
  import { RecruitEvents, Vacancy, Vacancy as VacancyClass } from '@hcengineering/recruit'
  import tags from '@hcengineering/tags'
  import task, { ProjectType, makeRank } from '@hcengineering/task'
  import { selectedTypeStore, typeStore } from '@hcengineering/task-resources'
  import tracker, { Issue, IssueStatus, IssueTemplate, IssueTemplateData, Project } from '@hcengineering/tracker'
  import {
    Button,
    Component,
    EditBox,
    FocusHandler,
    IconAttachment,
    createFocusManager,
    showPopup
  } from '@hcengineering/ui'
  import { createEventDispatcher } from 'svelte'
  import recruit from '../plugin'
  import Company from './icons/Company.svelte'
  import VacancyIcon from './icons/Vacancy.svelte'
  import { Analytics } from '@hcengineering/analytics'
  import { getSequenceId } from '../utils'
  import { isEmptyMarkup } from '@hcengineering/text'

  const dispatch = createEventDispatcher()

  let name: string = ''
  let template: ProjectType | undefined
  let typeId: Ref<ProjectType> | undefined = $selectedTypeStore

  $: typeType = typeId !== undefined ? $typeStore.get(typeId) : undefined

  let appliedTemplateId: Ref<ProjectType> | undefined
  let objectId: Ref<VacancyClass> = generateId()
  let issueTemplates: IssueTemplate[] = []
  let fullDescription: string = ''

  let members = [getCurrentAccount()._id]
  let membersChanged: boolean = false

  $: setDefaultMembers(typeType)

  function setDefaultMembers (typeType: ProjectType | undefined): void {
    if (typeType === undefined) return
    if (membersChanged) return
    if (typeType.members === undefined || typeType.members.length === 0) return
    members = typeType.members
  }

  export let company: Ref<Organization> | undefined
  export let preserveCompany: boolean = false

  let vacancyData: Data<VacancyClass> = {
    archived: false,
    description: '',
    members,
    name: '',
    number: 0,
    private: false,
    attachments: 0,
    comments: 0,
    company: '' as Ref<Organization>,
    fullDescription: null,
    location: '',
    type: typeId as Ref<ProjectType>
  }
  export function canClose (): boolean {
    return name.trim() === '' && typeId !== undefined
  }

  const client = getClient()
  const hierarchy = client.getHierarchy()
  const templateQ = createQuery()
  fillDefaults(hierarchy, vacancyData, recruit.class.Vacancy)
  $: typeId &&
    templateQ.query(task.class.ProjectType, { _id: typeId }, (result) => {
      const { _class, _id, description, targetClass, ...templateData } = result[0]
      vacancyData = { ...(templateData as unknown as Data<VacancyClass>) }
      if (appliedTemplateId !== typeId) {
        fullDescription = description ?? ''
        appliedTemplateId = typeId
      }
      fillDefaults(hierarchy, vacancyData, recruit.class.Vacancy)
    })

  const issueTemplatesQ = createQuery()
  $: issueTemplatesQ.query(tracker.class.IssueTemplate, { 'relations._id': typeId }, async (result) => {
    issueTemplates = result
  })

  let rolesAssignment: RolesAssignment | undefined
  let roles: Role[] = []
  const rolesQuery = createQuery()
  $: if (typeType !== undefined) {
    rolesQuery.query(
      core.class.Role,
      { attachedTo: typeType._id },
      (res) => {
        roles = res

        if (rolesAssignment === undefined) {
          rolesAssignment = roles.reduce<RolesAssignment>((prev, { _id }) => {
            prev[_id] = []

            return prev
          }, {})
        }
      },
      {
        sort: {
          name: SortingOrder.Ascending
        }
      }
    )
  } else {
    rolesQuery.unsubscribe()
  }

  async function saveIssue (
    id: Ref<VacancyClass>,
    space: Ref<Project>,
    template: IssueTemplateData,
    parent: Ref<Issue> = tracker.ids.NoParent
  ): Promise<Ref<Issue> | undefined> {
    const lastOne = await client.findOne<Issue>(
      tracker.class.Issue,
      { space },
      { sort: { rank: SortingOrder.Descending } }
    )
    const incResult = await client.updateDoc(
      tracker.class.Project,
      core.space.Space,
      space,
      {
        $inc: { sequence: 1 }
      },
      true
    )
    const project = await client.findOne(tracker.class.Project, { _id: space })
    const rank = makeRank(lastOne?.rank, undefined)
    const taskType = await client.findOne(task.class.TaskType, { ofClass: tracker.class.Issue })
    if (taskType === undefined) {
      return
    }
    const number = (incResult as any).object.sequence

    const resId: Ref<Issue> = generateId()
    const identifier = `${project?.identifier}-${number}`
    const data: AttachedData<Issue> = {
      title: template.title + ` (${name.trim()})`,
      description: null,
      assignee: template.assignee,
      component: template.component,
      milestone: template.milestone,
      number,
      status: project?.defaultIssueStatus as Ref<IssueStatus>,
      priority: template.priority,
      rank,
      comments: 0,
      subIssues: 0,
      dueDate: null,
      parents: [],
      reportedTime: 0,
      remainingTime: 0,
      estimation: template.estimation,
      reports: 0,
      relations: [{ _id: id, _class: recruit.class.Vacancy }],
      childInfo: [],
      kind: taskType._id,
      identifier
    }

    if (!isEmptyMarkup(template.description)) {
      const collabId = makeCollabId(tracker.class.Issue, resId, 'description')
      data.description = await createMarkup(collabId, template.description)
    }

    await client.addCollection(tracker.class.Issue, space, parent, tracker.class.Issue, 'subIssues', data, resId)
    if ((template.labels?.length ?? 0) > 0) {
      const tagElements = await client.findAll(tags.class.TagElement, { _id: { $in: template.labels } })
      for (const label of tagElements) {
        await client.addCollection(tags.class.TagReference, space, resId, tracker.class.Issue, 'labels', {
          title: label.title,
          color: label.color,
          tag: label._id
        })
      }
    }
    return resId
  }

  async function createVacancy (): Promise<void> {
    if (typeId === undefined || typeType === undefined) {
      throw Error(`Failed to find target project type: ${typeId}`)
    }

    const sequence = await client.findOne(task.class.Sequence, { attachedTo: recruit.class.Vacancy })
    if (sequence === undefined) {
      throw new Error('sequence object not found')
    }

    const incResult = await client.update(sequence, { $inc: { sequence: 1 } }, true)
    const data: Data<Vacancy> = {
      ...vacancyData,
      name: name.trim(),
      description: template?.shortDescription ?? '',
      fullDescription: null,
      private: false,
      archived: false,
      number: (incResult as any).object.sequence,
      company,
      members,
      autoJoin: typeType.autoJoin ?? false,
      owners: [getCurrentAccount()._id],
      type: typeId
    }

    if (!isEmptyMarkup(fullDescription)) {
      const collabId = makeCollabId(recruit.class.Vacancy, objectId, 'fullDescription')
      data.fullDescription = await createMarkup(collabId, fullDescription)
    }

    const ops = client.apply()
    const id = await ops.createDoc(recruit.class.Vacancy, core.space.Space, data, objectId)
    await descriptionBox.createAttachments(undefined, ops)
    await ops.commit()
    Analytics.handleEvent(RecruitEvents.VacancyCreated, {
      id: getSequenceId({
        ...data,
        _id: id,
        _class: recruit.class.Vacancy,
        space: core.space.Space,
        modifiedOn: 0,
        modifiedBy: getCurrentAccount()._id
      })
    })

    if (issueTemplates.length > 0) {
      for (const issueTemplate of issueTemplates) {
        const issue = await saveIssue(id, issueTemplate.space, issueTemplate)
        if (issue !== undefined) {
          for (const sub of issueTemplate.children) {
            await saveIssue(id, issueTemplate.space, sub, issue)
          }
        }
      }
    }

    // Add vacancy mixin with roles assignment
    await client.createMixin(
      objectId,
      recruit.class.Vacancy,
      core.space.Space,
      typeType.targetClass,
      rolesAssignment ?? {}
    )

    objectId = generateId()

    dispatch('close', id)
  }
  const manager = createFocusManager()

  let descriptionBox: AttachmentStyledBox

  function handleTypeChange (evt: CustomEvent<Ref<ProjectType>>): void {
    if (typeId == null) {
      typeId = evt.detail
      return
    }
    // Template is already specified, ask to replace.
    showPopup(
      MessageBox,
      {
        label: recruit.string.TemplateReplace,
        message: recruit.string.TemplateReplaceConfirm,
        okLabel: recruit.string.Apply
      },
      'top',
      (result?: boolean) => {
        if (result === true) {
          typeId = evt.detail ?? undefined
        }
      }
    )
  }

  function handleRoleAssignmentChanged (roleId: Ref<Role>, newMembers: Ref<Account>[]): void {
    if (rolesAssignment === undefined) {
      rolesAssignment = {}
    }

    rolesAssignment[roleId] = newMembers
  }
</script>

<FocusHandler {manager} />
<Card
  label={recruit.string.CreateVacancy}
  okAction={createVacancy}
  canSave={name.trim() !== ''}
  gap={'gapV-4'}
  on:close={() => {
    dispatch('close')
  }}
  on:changeContent
>
  <span>{typeType?.name}</span>
  <div class="flex-row-center clear-mins">
    <div class="mr-3">
      <Button focusIndex={1} icon={VacancyIcon} size={'medium'} kind={'link-bordered'} noFocus />
    </div>
    <EditBox
      focusIndex={2}
      bind:value={name}
      placeholder={recruit.string.VacancyPlaceholder}
      kind={'large-style'}
      autoFocus
    />
  </div>
  {#key appliedTemplateId}
    <AttachmentStyledBox
      bind:this={descriptionBox}
      {objectId}
      _class={recruit.class.Vacancy}
      space={objectId}
      alwaysEdit
      showButtons={false}
      maxHeight={'card'}
      bind:content={fullDescription}
      placeholder={recruit.string.FullDescription}
      kind={'emphasized'}
    />
  {/key}

  <svelte:fragment slot="header">
    <UserBox
      focusIndex={3}
      _class={contact.class.Organization}
      label={recruit.string.Company}
      placeholder={recruit.string.Company}
      justify={'left'}
      bind:value={company}
      allowDeselect
      titleDeselect={recruit.string.UnAssignCompany}
      kind={'regular'}
      size={'large'}
      icon={Company}
      readonly={preserveCompany}
      showNavigate={false}
      create={{ component: contact.component.CreateOrganization, label: contact.string.CreateOrganization }}
    />
    <Component
      is={task.component.ProjectTypeSelector}
      props={{
        descriptors: [recruit.descriptors.VacancyType],
        type: typeId,
        focusIndex: 4,
        kind: 'regular',
        size: 'large'
      }}
      on:change={handleTypeChange}
    />
  </svelte:fragment>

  <svelte:fragment slot="pool">
    <InlineAttributeBar
      _class={recruit.class.Vacancy}
      object={vacancyData}
      toClass={core.class.Space}
      ignoreKeys={['fullDescription', 'company', 'type']}
      extraProps={{ showNavigate: false }}
    />

    <AccountArrayEditor
      bind:value={members}
      label={contact.string.Members}
      onChange={() => {
        membersChanged = true
      }}
      kind={'regular'}
      size={'large'}
    />

    {#each roles as role}
      <AccountArrayEditor
        value={rolesAssignment?.[role._id] ?? []}
        label={getEmbeddedLabel(role.name)}
        emptyLabel={getEmbeddedLabel(role.name)}
        onChange={(refs) => {
          handleRoleAssignmentChanged(role._id, refs)
        }}
        kind={'regular'}
        size={'large'}
      />
    {/each}
  </svelte:fragment>
  <svelte:fragment slot="footer">
    <Button
      icon={IconAttachment}
      size={'large'}
      on:click={() => {
        descriptionBox.handleAttach()
      }}
    />
  </svelte:fragment>
</Card>