package app

import (
	
	
	
	
	
	
	
	

	
)

type engineX struct {
	ctx context.Context

	localStorage   BrowserStorage
	sessionStorage BrowserStorage
	browser        browser

	routes         *router
	internalURLs   []string
	resolveURL     func(string) string
	originPage     *requestPage
	lastVisitedURL *url.URL

	nodes   nodeManager
	updates updateManager
	body    HTMLBody

	dispatches chan func()
	defers     chan func()
	goroutines sync.WaitGroup

	asynchronousActionHandlers map[string]ActionHandler
	actions                    actionManager
	states                     stateManager
}

func newEngine( context.Context,  *router,  func(string) string,  *requestPage,  map[string]ActionHandler) *engineX {
	var  BrowserStorage
	var  BrowserStorage
	if IsServer {
		 = newMemoryStorage()
		 = newMemoryStorage()
	} else {
		 = newJSStorage("localStorage")
		 = newJSStorage("sessionStorage")
	}

	if  == nil {
		 = func( string) string { return  }
	}
	.resolveURL = 

	 := &engineX{
		ctx:                        ,
		routes:                     ,
		resolveURL:                 ,
		originPage:                 ,
		localStorage:               ,
		lastVisitedURL:             &url.URL{},
		sessionStorage:             ,
		nodes:                      nodeManager{},
		dispatches:                 make(chan func(), 4096),
		defers:                     make(chan func(), 4096),
		asynchronousActionHandlers: ,
	}

	.initBrowser()
	return 
}

func ( *engineX) () Context {
	return Context{
		Context:               .ctx,
		resolveURL:            .resolveURL,
		appUpdatable:          .browser.AppUpdatable,
		page:                  .page,
		navigate:              .Navigate,
		localStorage:          .localStorage,
		sessionStorage:        .sessionStorage,
		dispatch:              .dispatch,
		defere:                .defere,
		async:                 .async,
		addComponentUpdate:    .updates.Add,
		removeComponentUpdate: .updates.Done,
		handleAction:          .actions.Handle,
		postAction:            .actions.Post,
		observeState:          .states.Observe,
		getState:              .states.Get,
		setState:              .states.Set,
		delState:              .states.Delete,

		notifyComponentEvent: .nodes.NotifyComponentEvent,
	}
}

// Navigate directs the engine to the specified URL destination, which might be
// an internal page within the app, an external link outside the app, or a
// mailto link. If the 'updateHistory' flag is true, the destination is added to
// the browser's history.
func ( *engineX) ( *url.URL,  bool) {
	if .Host == "" {
		.Host = .originPage.URL().Host
	}

	switch {
	case .internalURL(),
		.mailTo():
		Window().Get("location").Set("href", .String())
		return

	case .externalNavigation():
		Window().Call("open", .String())
		return

	case .String() == .lastVisitedURL.String():
		return
	}

	defer func() {
		if  {
			Window().addHistory()
		}
		.lastVisitedURL = 

		.nodes.NotifyComponentEvent(.baseContext(), .body, nav{})

		if .Fragment != "" {
			.defere(func() {
				Window().ScrollToID(.Fragment)
			})
		}
	}()

	if .Path == .lastVisitedURL.Path &&
		.Fragment != .lastVisitedURL.Fragment {
		return
	}

	 := strings.TrimPrefix(.Path, Getenv("GOAPP_ROOT_PREFIX"))
	if !strings.HasPrefix(, "/") {
		 = "/" + 
	}
	,  := .routes.createComponent()
	if ! {
		 = &notFound{}
	}

	if  := .Load();  != nil {
		panic(errors.New("loading component failed").
			WithTag("component-type", reflect.TypeOf()).
			Wrap())
	}
}

func ( *engineX) () {
	if IsServer {
		return
	}
	.browser.HandleEvents(.baseContext(), .notifyComponentEvent)
}

func ( *engineX) ( any) {
	.nodes.NotifyComponentEvent(.baseContext(), .body, )
}

func ( *engineX) ( *url.URL) bool {
	return .Host != .originPage.URL().Host
}

func ( *engineX) ( *url.URL) bool {
	return .Scheme == "mailto"
}

func ( *engineX) ( *url.URL) bool {
	if .internalURLs == nil {
		json.Unmarshal([]byte(Getenv("GOAPP_INTERNAL_URLS")), &.internalURLs)
	}

	 := .String()
	for ,  := range .internalURLs {
		if strings.HasPrefix(, ) {
			return true
		}
	}
	return false
}

func ( *engineX) () Page {
	if IsClient {
		return makeBrowserPage(.resolveURL)
	}
	return .originPage
}

func ( *engineX) ( Composer) error {
	if .body == nil {
		 := Body()
		 = .setJSElement(Window().Get("document").Get("body")).(HTMLBody)

		 := Div()
		 = .setJSElement(.JSValue().firstElementChild()).(HTMLDiv)
		 = .setParent().(HTMLDiv)

		 = .setBody([]UI{}).(HTMLBody)
		.body = 

		for ,  := range .asynchronousActionHandlers {
			.actions.Handle(, , true, )
		}
	}

	,  := .nodes.Update(.baseContext(), .body, Body().privateBody())
	if  != nil {
		return errors.New("updating root failed").Wrap()
	}
	.body = .(HTMLBody)
	return nil
}

// Start initiates the main event loop of the engine at the specified framerate.
// The loop efficiently manages dispatches, component updates, and deferred
// actions.
func ( *engineX) ( int) {
	if  <= 0 {
		 = 30
	}

	 := time.Hour
	 := time.Second / time.Duration()
	 := time.Nanosecond
	 := time.NewTicker()
	defer .Stop()

	.states.CleanupExpiredPersistedStates(.baseContext())

	for {
		select {
		case  := <-.dispatches:
			if  !=  {
				.Reset()
				 = 
			}
			()

		case <-.C:
			.processFrame()
			.Reset()
			 = 

		case <-.ctx.Done():
			return
		}
	}
}

func ( *engineX) () {
	.updates.UpdateForEach(func( Composer) {
		if !.Mounted() {
			return
		}

		if ,  := .nodes.UpdateComponentRoot(.baseContext(), );  != nil {
			panic(errors.New("updating component failed").Wrap())
		}
	})
	.executeDefers()
	.actions.Cleanup()
	.states.Cleanup()
}

func ( *engineX) () {
	for {
		select {
		case  := <-.defers:
			()

		default:
			return
		}
	}
}

// ConsumeNext waits for any ongoing goroutines to finish, then executes the
// next dispatch in the queue. After executing the dispatch, it processes a
// frame.
func ( *engineX) () {
	.goroutines.Wait()
	 := <-.dispatches
	()
	.processFrame()
}

// ConsumeAll continuously waits for ongoing goroutines to finish, executes all
// available dispatches in the queue until none are left, and then processes a
// frame.
func ( *engineX) () {
	for {
		select {
		case  := <-.dispatches:
			()

		default:
			.processFrame()
			.goroutines.Wait()
			if len(.dispatches) == 0 {
				return
			}
		}
	}
}

// Encode serializes the given HTML element, integrating the engine's root
// component as the initial child within the document's body. The final HTML
// content, including the standard DOCTYPE declaration, is written  to the
// provided buffer.
func ( *engineX) ( *bytes.Buffer,  HTMLHtml) error {
	if .body == nil {
		return errors.New("no component loaded")
	}
	 := .body.body()[0]

	var  HTML
	for ,  := range .(HTML).body() {
		if ,  := .(HTMLBody);  {
			 = 
			break
		}
	}
	if  == nil {
		return errors.New("document does not have a body")
	}

	 := make([]UI, 0, len(.body())+1)
	 = append(, )
	 = append(, .body()...)
	.setBody()

	.WriteString("<!doctype html>\n")
	.nodes.Encode(.baseContext(), , )
	return nil
}

func ( *engineX) ( func()) {
	.dispatches <- 
}

func ( *engineX) ( func()) {
	.defers <- 
}

func ( *engineX) ( func()) {
	.goroutines.Add(1)
	go func() {
		()
		.goroutines.Done()
	}()
}