import Foundation
import Combine

final class Settings: ChangeObservableItem {

    @Published
    var general: GeneralSettings

    @Published
    var paths: PathSettings

    /// The items to show in the navigation bar
    @Published
    var navigation: NavigationSettings

    @Published
    var posts: PostSettings

    @Published
    var pages: PageSettings

    @Published
    var audioPlayer: AudioPlayerSettings

    @Published
    var tools: ToolSettings

    weak var content: Content?

    var cancellables: Set<AnyCancellable> = []

    init(general: GeneralSettings,
         paths: PathSettings,
         navigation: NavigationSettings,
         posts: PostSettings,
         pages: PageSettings,
         audioPlayer: AudioPlayerSettings,
         tools: ToolSettings) {
        self.general = general
        self.paths = paths
        self.navigation = navigation
        self.posts = posts
        self.pages = pages
        self.audioPlayer = audioPlayer
        self.tools = tools
        observeChildChanges()
    }

    func remove(_ file: FileResource) {
        pages.remove(file)
        posts.remove(file)
        audioPlayer.remove(file)
    }

    func needsSaving() {
        content?.needsSave()
    }

    private func observeChildChanges() {
        observe(general)
        observe(paths)
        observe(navigation)
        observe(posts)
        observe(pages)
        observe(audioPlayer)
        observe(tools)
    }
}

// MARK: Storage

extension Settings {

    convenience init(context: LoadingContext, data: Settings.Data) {
        self.init(
            general: .init(context: context, data: data.general),
            paths: .init(data: data.paths),
            navigation: .init(context: context, data: data.navigation),
            posts: .init(context: context, data: data.posts),
            pages: .init(context: context, data: data.pages),
            audioPlayer: .init(context: context, data: data.audioPlayer),
            tools: .init(context: context, data: data.tools))
        content = context.content
    }

    func data(tagOverview: Tag?) -> Data {
        .init(
            general: general.data,
            paths: paths.data,
            navigation: navigation.data,
            posts: posts.data,
            pages: pages.data,
            audioPlayer: audioPlayer.data,
            tagOverview: tagOverview?.data,
            tools: tools.data)
    }

    struct Data: Codable, Equatable {
        let general: GeneralSettings.Data
        let paths: PathSettings.Data
        let navigation: NavigationSettings.Data
        let posts: PostSettings.Data
        let pages: PageSettings.Data
        let audioPlayer: AudioPlayerSettings.Data
        let tagOverview: Tag.Data?
        let tools: ToolSettings.Data
    }

    func saveToDisk(_ data: Data) -> Bool {
        content?.storage.save(settings: data) ?? false
    }
}

extension Settings {

    static let `default`: Settings = .init(
        general: .default,
        paths: .default,
        navigation: .default,
        posts: .default,
        pages: .default,
        audioPlayer: .default,
        tools: .default)
}

extension GeneralSettings {

    static let `default`: GeneralSettings = .init(
        url: "https://example.com",
        linkPreviewImageWidth: 1200,
        linkPreviewImageHeight: 630,
        remoteUserForUpload: "user",
        remotePortForUpload: 22,
        remotePathForUpload: "/home/user/web",
        urlForPushNotification: nil,
        requiredFiles: [])
}

extension AudioPlayerSettings {

    static let `default`: AudioPlayerSettings = .init(
        playlistCoverImageSize: 280,
        smallCoverImageSize: 78,
        audioPlayerJsFile: nil,
        audioPlayerCssFile: nil,
        german: .init(playlistText: "Wiedergabeliste"),
        english: .init(playlistText: "Playlist"))
}

extension PostSettings {

    static var `default`: PostSettings {
        .init(postsPerPage: 25,
              contentWidth: 600,
              swiperCssFile: nil,
              swiperJsFile: nil,
              defaultCssFile: nil,
              german: .init(
                feedUrlPrefix: "blog",
                defaultPageLinkText: "Anzeigen",
                linkPreview: .init(
                    title:  "Beiträge",
                    description: "Alle Beiträge")),
              english: .init(
                feedUrlPrefix: "blog",
                defaultPageLinkText: "View",
                linkPreview: .init(
                    title:  "Blog posts",
                    description: "All blog posts"))
        )
    }
}

extension PathSettings {

    static var `default`: PathSettings {
        .init(
            assetsOutputFolderPath: "asset",
            pagesOutputFolderPath: "page",
            imagesOutputFolderPath: "image",
            filesOutputFolderPath: "file",
            videosOutputFolderPath: "video",
            audioOutputFolderPath: "audio",
            tagsOutputFolderPath: "tag")
    }

}

extension PageSettings {

    static var `default`: PageSettings {
        .init(contentWidth: 600,
              largeImageWidth: 1200,
              pageLinkImageSize: 180,
              defaultCssFile: nil,
              codeHighlightingJsFile: nil,
              modelViewerJsFile: nil,
              imageCompareJsFile: nil,
              imageCompareCssFile: nil,
              german: .init(
                emptyPageTitle: "Leere Seite",
                emptyPageText: "Diese Seite ist leer"),
              english: .init(
                emptyPageTitle: "Empty page",
                emptyPageText: "This page is empty"))
    }
}

extension ToolSettings {

    static var `default`: ToolSettings {
        .init(ffprobePath: "/opt/homebrew/bin/ffprobe",
              avifencPath: "/opt/homebrew/bin/avifenc")
    }
}
