import Foundation

final class Post: Item, DateItem, LocalizedItem {

    override var itemType: ItemType { .post }

    @Published
    var isDraft: Bool

    @Published
    var createdDate: Date

    @Published
    var startDate: Date

    @Published
    var hasEndDate: Bool

    @Published
    var potentialEndDate: Date

    /**
     The tags associated with the post

     This list is only used if ``linkedPage`` is `nil`,
     otherwise the tag list of the linked page is used.
     */
    @Published
    var tags: [Tag]

    @Published
    var german: LocalizedPost

    @Published
    var english: LocalizedPost

    /// The page linked to by this post
    @Published
    var linkedPage: Page?

    /// The workouts associated with the post
    @Published
    var associatedWorkouts: [FileResource]

    init(content: Content,
         id: String,
         isDraft: Bool,
         createdDate: Date,
         startDate: Date,
         endDate: Date?,
         tags: [Tag],
         german: LocalizedPost,
         english: LocalizedPost,
         linkedPage: Page? = nil,
         associatedWorkouts: [FileResource] = []) {
        self.isDraft = isDraft
        self.createdDate = createdDate
        self.startDate = startDate
        self.hasEndDate = endDate != nil
        self.potentialEndDate = endDate ?? startDate
        self.tags = tags
        self.german = german
        self.english = english
        self.linkedPage = linkedPage
        self.associatedWorkouts = associatedWorkouts
        super.init(content: content, id: id)
    }

    // MARK: Storage

    var savedData: Data?

    // MARK: Tags

    func usedTags() -> [Tag] {
        linkedPage?.tags ?? tags
    }

    /**
     Check if a tag is associated with this post.

     If the post is linked to a page, then the tags of the page are checked.
     */
    func contains(_ tag: Tag) -> Bool {
        guard let linkedPage else {
            return tags.contains(tag)
        }
        return linkedPage.tags.contains(tag)
    }

    func toggle(_ tag: Tag) {
        if let linkedPage {
            linkedPage.toggle(tag)
            didChange() // Otherwise tags will not be updated
            return
        }
        guard let index = tags.firstIndex(of: tag) else {
            tags.append(tag)
            return
        }
        tags.remove(at: index)
    }

    func remove(_ tag: Tag) {
        if let linkedPage {
            linkedPage.remove(tag)
            return
        }
        tags.remove(tag)
    }

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

    /**
     A title for the UI, not the generation.
     */
    override func title(in language: ContentLanguage) -> String {
        localized(in: language).title ?? identifier
    }

    func contains(_ string: String) -> Bool {
        identifier.contains(string) ||
        german.contains(string) ||
        english.contains(string)
    }

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

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

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

    func makePage() -> Page {
        var id = self.identifier
        var number = 2
        while !content.isNewIdForPage(id) {
            id += "\(self.identifier)-\(number)"
            number += 1
        }
        // Move tags to page
        let tags = self.tags
        self.tags = []

        let german = german.makePage(urlString: id + "-de")
        let english = english.makePage(urlString: id + "-en")
        return Page(
            content: content,
            id: id,
            externalLink: nil,
            isDraft: true,
            createdDate: .now,
            hideDate: false,
            startDate: startDate,
            endDate: endDate,
            german: german,
            english: english,
            tags: tags)
    }

    func updateLabelsFromWorkout() {
        let workouts = associatedWorkouts.compactMap { $0.routeOverview }
        guard !workouts.isEmpty else {
            return
        }
        let overview = RouteOverview.combine(workouts)
        overview.update(labels: &german.labels, language: .german)
        overview.update(labels: &english.labels, language: .english)
    }
}

extension Post: StorageItem {

    convenience init(context: LoadingContext, id: String, data: Data) {
        self.init(
            content: context.content,
            id: id,
            isDraft: data.isDraft,
            createdDate: data.createdDate,
            startDate: data.startDate,
            endDate: data.endDate,
            tags: data.tags.compactMap(context.tag),
            german: .init(context: context, data: data.german),
            english: .init(context: context, data: data.english),
            linkedPage: data.linkedPageId.map(context.page),
            associatedWorkouts: data.associatedWorkoutIds?.compactMap(context.file) ?? [])
        savedData = data
    }

    struct Data: Codable, Equatable {
        let isDraft: Bool
        let createdDate: Date
        let startDate: Date
        let endDate: Date?
        let tags: [String]
        let german: LocalizedPost.Data
        let english: LocalizedPost.Data
        let linkedPageId: String?
        let associatedWorkoutIds: [String]?
    }

    var data: Data {
        .init(
            isDraft: isDraft,
            createdDate: createdDate,
            startDate: startDate,
            endDate: endDate,
            tags: tags.map { $0.identifier },
            german: german.data,
            english: english.data,
            linkedPageId: linkedPage?.identifier,
            associatedWorkoutIds: associatedWorkouts.map { $0.identifier}.nonEmpty )
    }

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