import Foundation

final class Page: Item, DateItem, LocalizedItem {

    override var itemType: ItemType { .page }

    /**
     The external link this page points to.

     If this value is not `nil`, then the page has no content
     and many other features are disabled.
     */
    @Published
    var externalLink: String?

    @Published
    var isDraft: Bool

    @Published
    var createdDate: Date

    @Published
    var hideDate: Bool

    @Published
    var startDate: Date

    @Published
    var hasEndDate: Bool

    @Published
    var potentialEndDate: Date

    @Published
    var german: LocalizedPage

    @Published
    var english: LocalizedPage

    @Published
    var tags: [Tag]

    var savedData: Data?

    init(content: Content,
         id: String,
         externalLink: String?,
         isDraft: Bool,
         createdDate: Date,
         hideDate: Bool,
         startDate: Date,
         endDate: Date?,
         german: LocalizedPage,
         english: LocalizedPage,
         tags: [Tag]) {
        self.externalLink = externalLink
        self.isDraft = isDraft
        self.createdDate = createdDate
        self.hideDate = hideDate
        self.startDate = startDate
        self.hasEndDate = endDate != nil
        self.potentialEndDate = endDate ?? startDate
        self.german = german
        self.english = english
        self.tags = tags

        super.init(content: content, id: id)
    }

    func isValid(id: String) -> Bool {
        !id.isEmpty &&
        content.isValidIdForTagOrPageOrPost(id) &&
        content.isNewIdForPage(id)
    }

    @discardableResult
    func update(id newId: String) -> Bool {
        guard content.storage.move(page: identifier, to: newId) else {
            print("Failed to move files of page \(identifier)")
            return false
        }
        identifier = newId
        return true
    }

    var isExternalUrl: Bool {
        externalLink != nil
    }

    // MARK: Tags

    /**
     Check if a tag is associated with this page
     */
    func contains(_ tag: Tag) -> Bool {
        tags.contains(tag)
    }

    func toggle(_ tag: Tag) {
        guard let index = tags.firstIndex(of: tag) else {
            tags.append(tag)
            return
        }
        tags.remove(at: index)
    }

    func remove(_ tag: Tag) {
        tags.remove(tag)
    }

    func associate(_ tag: Tag) {
        guard !tags.contains(tag) else {
            return
        }
        tags.append(tag)
    }

    // MARK: Paths

    override func absoluteUrl(in language: ContentLanguage) -> String {
        if let url = externalLink {
            return url
        }
        // TODO: Record link to trace connections between pages
        return makeCleanAbsolutePath(internalPath(for: language))
    }

    override func title(in language: ContentLanguage) -> String {
        localized(in: language).title
    }

    func filePathRelativeToOutputFolder(for language: ContentLanguage) -> String {
        makeCleanRelativePath(internalPath(for: language))
    }

    private func internalPath(for language: ContentLanguage) -> String {
        content.settings.paths.pagesOutputFolderPath + "/" + localized(in: language).urlString
    }

    override var itemReference: ItemReference {
        .page(self)
    }

    func contains(urlComponent: String) -> Bool {
        english.urlString == urlComponent || german.urlString == urlComponent
    }

    func pageContent(in language: ContentLanguage) -> String? {
        content.storage.pageContent(for: identifier, language: language)
    }

    func removeContent(in language: ContentLanguage) -> Bool {
        guard content.storage.remove(pageContent: identifier, in: language) else {
            return false
        }
        if localized(in: language).update(hasContent: false) {
            self.didChange()
        }
        return true
    }

    func save(pageContent: String, in language: ContentLanguage) -> Bool {
        guard content.storage.save(pageContent: pageContent, for: identifier, in: language) else {
            return false
        }
        if localized(in: language).update(hasContent: true) {
            self.didChange()
        }
        return true
    }

    /**
     Update the `hasContent` property of all localized pages.
     */
    func updateContentExistence() {
        var didUpdate = false
        for language in ContentLanguage.allCases {
            let hasContent = content.storage.hasPageContent(for: identifier, language: language)
            if localized(in: language).update(hasContent: hasContent) {
                didUpdate = true
            }
        }
        if didUpdate {
            self.didChange()
        }
    }

    /// All languages for which the page has no content
    var missingContentLanguages: [ContentLanguage] {
        ContentLanguage.allCases.filter { !localized(in: $0).hasContent }
    }

    func remove(_ file: FileResource) {
        english.linkPreview.remove(file)
        german.linkPreview.remove(file)
    }
}

// MARK: Storage

extension Page: StorageItem {

    convenience init(context: LoadingContext, id: String, data: Data) {
        self.init(
            content: context.content,
            id: id,
            externalLink: data.externalLink,
            isDraft: data.isDraft,
            createdDate: data.createdDate,
            hideDate: data.hideDate ?? false,
            startDate: data.startDate,
            endDate: data.endDate,
            german: .init(context: context, data: data.german),
            english: .init(context: context, data: data.english),
            tags: data.tags.compactMap(context.tag))
        savedData = data
    }

    /// The structure to store the metadata of a page on disk
    struct Data: Codable, Equatable {
        let isDraft: Bool
        let externalLink: String?
        let tags: [String]
        let hideDate: Bool?
        let createdDate: Date
        let startDate: Date
        let endDate: Date?
        let german: LocalizedPage.Data
        let english: LocalizedPage.Data
    }


    var data: Data {
        .init(
            isDraft: isDraft,
            externalLink: externalLink,
            tags: tags.map { $0.identifier },
            hideDate: hideDate ? true : nil,
            createdDate: createdDate,
            startDate: startDate,
            endDate: endDate,
            german: german.data,
            english: english.data)
    }

    func saveToDisk(_ data: Data) -> Bool {
        content.storage.save(page: data, for: identifier)
    }
}
