sourceafPillow::PageMeta.fan

using afIoc::Inject
using afBedSheet::ValueEncoders
using afBedSheet::BedSheetServer
using afBedSheet::HttpRequest

** (Service) - Returns details about the Pillow page currently being rendered.
** 
** 'PageMeta' objects may also be created by the `Pages` service.
class PageMeta {

    internal        BedSheetServer  bedServer
    internal        HttpRequest     httpRequest
    internal        ValueEncoders   valueEncoders
    private const   PageMetaState   pageState
    private         Obj?[]          pageCtx
    
    internal new make(PageMetaState pageState, Obj?[]? pageContext, |This|in) {
        in(this)
        this.pageState  = pageState
        this.pageCtx    = pageContext ?: Obj#.emptyList
    }
    
    ** Returns the page that this Meta object wraps.
    Type pageType() {
        pageState.pageType
    }

    ** Returns the context used to initialise this page.
    Obj?[] pageContext() {
        pageCtx
    }

    @NoDoc @Deprecated { msg="Use 'pageUrl' instead" }
    Uri pageUri() {
        pageUrl
    }

    ** Returns a URI that can be used to render the given page. 
    ** The URI takes into account:
    **  - Any welcome URI -> home page conversions
    **  - The context used to render this page
    **  - Any parent 'WebMods'
    Uri pageUrl() {
        clientUrl := pageState.pageBaseUri

        // add extra WebMod path info
        clientUrl = bedServer.toClientUrl(clientUrl)

        // append page context
        contextTypes := contextTypes
        if (contextTypes.size != pageContext.size)
            throw Err(ErrMsgs.invalidNumberOfInitArgs(pageType, contextTypes, pageContext))
        if (!contextTypes.isEmpty)
            clientUrl = clientUrl.plusSlash + ctxToUri(pageContext)

        return clientUrl
    }
    
    ** Returns the 'Content-Type' produced by this page.
    ** 
    ** Returns `PillowConfigIds#defaultContextType` if it can not be determined.
    MimeType contentType() {
        pageState.contentType
    }
    
    ** Returns 'true' if the page is a welcome page.
    Bool isWelcomePage() {
        pageState.isWelcomePage
    }

    ** Returns the HTTP method this page responds to.
    Str httpMethod() {
        pageState.httpMethod
    }

    @NoDoc @Deprecated { msg="Use 'eventUrl' instead" }
    Uri eventUri(Str eventName, Obj?[]? eventContext := null) {
        eventUrl(eventName, eventContext)
    }

    ** Returns a URI for a given event - use to create client side URIs to call the event.
    Uri eventUrl(Str eventName, Obj?[]? eventContext := null) {
        eventMethod(eventName)      
        eventUrl    := pageUrl.plusSlash + `${eventName}`
        if (eventContext != null)
            eventUrl = eventUrl.plusSlash + ctxToUri(eventContext)
        return eventUrl     
    }

    ** Returns a new 'PageMeta' with the given page context.
    PageMeta withContext(Obj?[]? pageContext) {
        PageMeta(pageState, pageContext) {
            it.bedServer     = this.bedServer
            it.httpRequest   = this.httpRequest
            it.valueEncoders = this.valueEncoders
        }
    }
    
    ** Returns all the event methods on the page.
    Method[] eventMethods() {
        pageType.methods.findAll { it.hasFacet(PageEvent#) }        
    }

    @NoDoc
    Uri serverGlob() {
        pageState.serverGlob
    }
    
    @NoDoc
    Uri eventGlob(Method eventMethod) {
        pageEvent   := (PageEvent?) Method#.method("facet").callOn(eventMethod, [PageEvent#, false])    // Stoopid F4   
        if (pageEvent == null)
            throw ArgErr("WTF: Method '${eventMethod.qname}' does not have a @${PageEvent#.name} facet.")
        
        eventStr    := pageEvent.name ?: eventMethod.name
        noOfParams  := eventMethod.params.size
        noOfParams.times { eventStr += "/*" }
        return eventStr.toUri
    }
    
    @NoDoc
    Type[] contextTypes() {
        pageState.contextTypes
    }
    
    ** Returns 'pageUrl'.
    override Str toStr() {
        pageUrl.toStr
    }
    
    private Uri ctxToUri(Obj?[] context) {
        context.map { valueEncoders.toClient(it.typeof, it) ?: "" }.join("/").toUri     
    }
    
    private Method eventMethod(Str eventName) {
        eventMethods.find |method->Bool| {
            pageEvent := (PageEvent) Method#.method("facet").callOn(method, [PageEvent#])   // Stoopid F4   
            return eventName.equalsIgnoreCase(pageEvent.name ?: Str.defVal) || eventName.equalsIgnoreCase(method.name)  
        } ?: throw PillowErr(ErrMsgs.eventNotFound(pageType, eventName))        
    }

    internal static Obj? push(PageMeta pageMeta, |->Obj?| f) {
        ThreadStack.pushAndRun("afPillow.renderingPageMeta", pageMeta, f)
    }
    
    internal static PageMeta? peek(Bool checked) {
        ThreadStack.peek("afPillow.renderingPageMeta", false) ?: (checked ? throw PillowErr(ErrMsgs.renderingPageMetaNotRendering) : null)
    }
}