import { NetworkRequestFactoryInterface } from '@networking/NetworkRequestFactory'
import { NetworkClientInterface } from '@networking/NetworkClient'
import { PublishResponse } from '@networking/PublishResponse'
import { TagSuggestionDelegate, TagSuggestionViewModel } from '@views/TagSuggestionViewModel'
import { ViewModelFactoryInterface } from '@factories/ViewModelFactory'
import { Editor } from 'obsidian'
import { ServiceFactoryInterface } from '@base/factories/ServiceFactory'
import { FrontmatterService, FrontmatterServiceInterface } from '@base/services/FrontmatterService'

/*
 * Publish View Delegate Interface, implemented by
 * the object which needs to observe events from the view model.
 */
export interface PublishViewModelDelegate {

    // Triggered when user clicks the delete button when the
    // title property is reset.
    publishDidClearTitle(): void

    // triggered when user clicks the clear button when the
    // date property is reset.
    publishDidClearDate(): void

    // Triggered when publishing a new post succeeds.
    publishDidSucceed(response: PublishResponse): void

    // Triggered when publishing a new post fails.
    publishDidFail(error: Error): void

    // Triggered when selecting a tag from the picker.
    publishDidSelectTag(): void

    // Triggered after checking whether the scheduled date
    // is valid or not. It returns `true` for no date or for
    // valid date, and false for invalid dates.
    publishDidValidateDate(): void
}

/*
 * This view model drives the content and interactions with the
 * publish view.
 */
export class PublishViewModel implements TagSuggestionDelegate {

    // Properties

    public delegate?: PublishViewModelDelegate
    private isValidDate: boolean
    private isSubmitting: boolean
    private titleWrappedValue: string
    private content: string
    private visibilityWrappedValue: string
    private tagsWrappedValue: string
    private selectedBlogIDWrappedValue: string
    private scheduledDateWrappedValue: string
    private networkClient: NetworkClientInterface
    private networkRequestFactory: NetworkRequestFactoryInterface
    private viewModelFactory: ViewModelFactoryInterface
    private frontmatterService: FrontmatterServiceInterface
    private editor: Editor
    readonly blogs: Record<string, string>

    // Life cycle

    constructor(
        title: string,
        content: string,
        tags: string,
        visibility: string,
        blogs: Record<string, string>,
        selectedBlogID: string,
        networkClient: NetworkClientInterface,
        networkRequestFactory: NetworkRequestFactoryInterface,
        viewModelFactory: ViewModelFactoryInterface,
        frontmatterService: FrontmatterServiceInterface,
        editor: Editor
    ) {
        this.titleWrappedValue = title
        this.content = content
        this.tagsWrappedValue = tags
        this.visibilityWrappedValue = visibility
        this.blogs = blogs
        this.selectedBlogIDWrappedValue = selectedBlogID
        this.scheduledDateWrappedValue = ''
        this.isValidDate = true
        this.isSubmitting = false
        this.networkClient = networkClient
        this.networkRequestFactory = networkRequestFactory
        this.viewModelFactory = viewModelFactory
        this.frontmatterService = frontmatterService
        this.editor = editor
    }

    // Public

    public get title(): string {
        return this.titleWrappedValue
    }

    public set title(value: string) {
        this.titleWrappedValue = value
    }

    public get tags(): string {
        return this.tagsWrappedValue
    }

    public set tags(value: string) {
        this.tagsWrappedValue = value
    }

    public get visibility(): string {
        return this.visibilityWrappedValue
    }

    public set visibility(value: string) {
        this.visibilityWrappedValue = value
    }

    public get hasMultipleBlogs(): boolean {
        return Object.keys(this.blogs).length > 2
    }

    public get selectedBlogID(): string {
        return this.selectedBlogIDWrappedValue
    }

    public set selectedBlogID(value: string) {
        this.selectedBlogIDWrappedValue = value
    }

    public get scheduledDate(): string {
        return this.scheduledDateWrappedValue
    }

    public set scheduledDate(value: string) {
        this.scheduledDateWrappedValue = value
    }

    public get showPublishingButton(): boolean {
        return this.isValidDate && this.isSubmitting
    }

    public get invalidDateText(): string {
        return this.isValidDate ? "" : "Invalid date format"
    }

    public async publishNote() {
        if (!this.isValidScheduledDate()) {
            this.isValidDate = false
            this.isSubmitting = false
            this.delegate?.publishDidValidateDate()
            return
        }

        this.isValidDate = true
        this.isSubmitting = true
        this.delegate?.publishDidValidateDate()

        try {
            const request = this.networkRequestFactory.makePublishRequest(
                this.title,
                this.content,
                this.tags,
                this.visibility,
                this.selectedBlogID,
                this.formattedScheduledDate()
            )

            const response: PublishResponse = await this.networkClient.publish(
                request
            )

            this.frontmatterService.updateFrontmatter(response, this.editor)

            this.delegate?.publishDidSucceed(response)
        } catch (error) {
            this.delegate?.publishDidFail(error)
        }
    }

    public clearTitle() {
        this.title = ''
        this.delegate?.publishDidClearTitle()
    }

    public suggestionsViewModel(): TagSuggestionViewModel {
        const excluding = this.tags
            .split(',')
            .filter(value => value.length > 0)
            .map(tag => tag.trim())

        return this.viewModelFactory.makeTagSuggestionViewModel(
            excluding,
            this
        )
    }

    public clearDate() {
        this.scheduledDateWrappedValue = ''
        this.isValidDate = true
        this.delegate?.publishDidClearDate()
    }

    // Private

    private isValidScheduledDate(): boolean {
        const scheduledDate = new Date(this.scheduledDateWrappedValue)
        const isInvalidDate = isNaN(scheduledDate.getTime())

        if (this.scheduledDateWrappedValue.length > 0 && isInvalidDate) {
            return false
        }

        return true
    }

    private formattedScheduledDate(): string {
        const scheduledDate = new Date(this.scheduledDateWrappedValue.trim())
        const isInvalidDate = isNaN(scheduledDate.getTime())

        if (isInvalidDate) {
            return ''
        }

        return scheduledDate.toISOString()
    }

    // TagSuggestionDelegate

    public tagSuggestionDidSelectTag(category: string) {
        const tags = this.tags
            .split(',')
            .filter(value => value.length > 0)
            .map(tag => tag.trim())

        tags.push(category)

        const formattedTags = tags
            .filter((tag, index) => index === tags.indexOf(tag))
            .join()

        this.tags = formattedTags
        this.delegate?.publishDidSelectTag()
    }
}