// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

package opml // import "miniflux.app/v2/internal/reader/opml"

import (
	"encoding/xml"
	"fmt"
	"strconv"
	"strings"
)

const minifluxOPMLNamespace = "https://miniflux.app/opml"

// Specs: http://opml.org/spec2.opml
type opmlDocument struct {
	XMLName           xml.Name              `xml:"opml"`
	Version           string                `xml:"version,attr"`
	MinifluxNamespace string                `xml:"xmlns:miniflux,attr,omitempty"`
	Header            opmlHeader            `xml:"head"`
	Outlines          opmlOutlineCollection `xml:"body>outline"`
}

type opmlHeader struct {
	Title       string `xml:"title,omitempty"`
	DateCreated string `xml:"dateCreated,omitempty"`
	OwnerName   string `xml:"ownerName,omitempty"`
}

type opmlOutline struct {
	Title       string                `xml:"title,attr,omitempty"`
	Text        string                `xml:"text,attr"`
	FeedURL     string                `xml:"xmlUrl,attr,omitempty"`
	SiteURL     string                `xml:"htmlUrl,attr,omitempty"`
	Description string                `xml:"description,attr,omitempty"`
	Outlines    opmlOutlineCollection `xml:"outline,omitempty"`

	// Miniflux-specific feed settings
	ScraperRules                string `xml:"miniflux:scraperRules,attr,omitempty"`
	RewriteRules                string `xml:"miniflux:rewriteRules,attr,omitempty"`
	UrlRewriteRules             string `xml:"miniflux:urlRewriteRules,attr,omitempty"`
	BlocklistRules              string `xml:"miniflux:blocklistRules,attr,omitempty"`
	KeeplistRules               string `xml:"miniflux:keeplistRules,attr,omitempty"`
	BlockFilterEntryRules       string `xml:"miniflux:blockFilterEntryRules,attr,omitempty"`
	KeepFilterEntryRules        string `xml:"miniflux:keepFilterEntryRules,attr,omitempty"`
	UserAgent                   string `xml:"miniflux:userAgent,attr,omitempty"`
	Crawler                     bool   `xml:"miniflux:crawler,attr,omitempty"`
	IgnoreHTTPCache             bool   `xml:"miniflux:ignoreHTTPCache,attr,omitempty"`
	FetchViaProxy               bool   `xml:"miniflux:fetchViaProxy,attr,omitempty"`
	Disabled                    bool   `xml:"miniflux:disabled,attr,omitempty"`
	NoMediaPlayer               bool   `xml:"miniflux:noMediaPlayer,attr,omitempty"`
	HideGlobally                bool   `xml:"miniflux:hideGlobally,attr,omitempty"`
	AllowSelfSignedCertificates bool   `xml:"miniflux:allowSelfSignedCertificates,attr,omitempty"`
	DisableHTTP2                bool   `xml:"miniflux:disableHTTP2,attr,omitempty"`
	IgnoreEntryUpdates          bool   `xml:"miniflux:ignoreEntryUpdates,attr,omitempty"`
}

func (o opmlOutline) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
	type opmlOutlineXml opmlOutline

	outlineType := ""
	if o.IsSubscription() {
		outlineType = "rss"
	}

	return e.EncodeElement(struct {
		opmlOutlineXml
		Type string `xml:"type,attr,omitempty"`
	}{
		opmlOutlineXml: opmlOutlineXml(o),
		Type:           outlineType,
	}, start)
}

func (o *opmlOutline) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
	*o = opmlOutline{}

	type opmlOutlineAlias opmlOutline
	if err := d.DecodeElement((*opmlOutlineAlias)(o), &start); err != nil {
		return err
	}

	for _, attr := range start.Attr {
		if attr.Name.Space == minifluxOPMLNamespace {
			if err := o.setMinifluxAttribute(attr.Name.Local, attr.Value); err != nil {
				return err
			}
		}
	}

	return nil
}

func (o opmlOutline) IsSubscription() bool {
	return strings.TrimSpace(o.FeedURL) != ""
}

func (o opmlOutline) GetTitle() string {
	if o.Title != "" {
		return o.Title
	}

	if o.Text != "" {
		return o.Text
	}

	if o.SiteURL != "" {
		return o.SiteURL
	}

	if o.FeedURL != "" {
		return o.FeedURL
	}

	return ""
}

func (o opmlOutline) GetSiteURL() string {
	if o.SiteURL != "" {
		return o.SiteURL
	}

	return o.FeedURL
}

type opmlOutlineCollection []opmlOutline

func (o opmlOutlineCollection) HasChildren() bool {
	return len(o) > 0
}

func (o *opmlOutline) setMinifluxAttribute(name, value string) error {
	switch name {
	case "scraperRules":
		o.ScraperRules = value
	case "rewriteRules":
		o.RewriteRules = value
	case "urlRewriteRules":
		o.UrlRewriteRules = value
	case "blocklistRules":
		o.BlocklistRules = value
	case "keeplistRules":
		o.KeeplistRules = value
	case "blockFilterEntryRules":
		o.BlockFilterEntryRules = value
	case "keepFilterEntryRules":
		o.KeepFilterEntryRules = value
	case "userAgent":
		o.UserAgent = value
	case "crawler":
		return setMinifluxBoolAttribute(name, value, &o.Crawler)
	case "ignoreHTTPCache":
		return setMinifluxBoolAttribute(name, value, &o.IgnoreHTTPCache)
	case "fetchViaProxy":
		return setMinifluxBoolAttribute(name, value, &o.FetchViaProxy)
	case "disabled":
		return setMinifluxBoolAttribute(name, value, &o.Disabled)
	case "noMediaPlayer":
		return setMinifluxBoolAttribute(name, value, &o.NoMediaPlayer)
	case "hideGlobally":
		return setMinifluxBoolAttribute(name, value, &o.HideGlobally)
	case "allowSelfSignedCertificates":
		return setMinifluxBoolAttribute(name, value, &o.AllowSelfSignedCertificates)
	case "disableHTTP2":
		return setMinifluxBoolAttribute(name, value, &o.DisableHTTP2)
	case "ignoreEntryUpdates":
		return setMinifluxBoolAttribute(name, value, &o.IgnoreEntryUpdates)
	}

	return nil
}

func setMinifluxBoolAttribute(name, value string, target *bool) error {
	parsedValue, err := strconv.ParseBool(value)
	if err != nil {
		return fmt.Errorf("opml: invalid miniflux attribute %q: %w", name, err)
	}

	*target = parsedValue
	return nil
}
